diff --git a/.travis.yml b/.travis.yml index 4d6f37d8ba8b..4a41176953e0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,7 +5,7 @@ matrix: script: >- ./config/travis/run-checks.sh && - travis_retry ./gradlew clean checkstyleMain checkstyleTest headless allTests coverage coveralls asciidoctor copyDummySearchPage + travis_retry ./gradlew clean checkstyleMain checkstyleTest headless allTests coverage coveralls asciidoctor deploy: skip_cleanup: true diff --git a/Collate-GUI.jar b/Collate-GUI.jar new file mode 100644 index 000000000000..3f9e6de8069c Binary files /dev/null and b/Collate-GUI.jar differ diff --git a/README.adoc b/README.adoc index 03eff3a4d191..2aa072ca68ca 100644 --- a/README.adoc +++ b/README.adoc @@ -1,11 +1,9 @@ -= Address Book (Level 4) += TuitionConnect (TC) ifdef::env-github,env-browser[:relfileprefix: docs/] -https://travis-ci.org/se-edu/addressbook-level4[image:https://travis-ci.org/se-edu/addressbook-level4.svg?branch=master[Build Status]] -https://ci.appveyor.com/project/damithc/addressbook-level4[image:https://ci.appveyor.com/api/projects/status/3boko2x2vr5cc3w2?svg=true[Build status]] -https://coveralls.io/github/se-edu/addressbook-level4?branch=master[image:https://coveralls.io/repos/github/se-edu/addressbook-level4/badge.svg?branch=master[Coverage Status]] -https://www.codacy.com/app/damith/addressbook-level4?utm_source=github.com&utm_medium=referral&utm_content=se-edu/addressbook-level4&utm_campaign=Badge_Grade[image:https://api.codacy.com/project/badge/Grade/fc0b7775cf7f4fdeaf08776f3d8e364a[Codacy Badge]] -https://gitter.im/se-edu/Lobby[image:https://badges.gitter.im/se-edu/Lobby.svg[Gitter chat]] +https://travis-ci.org/CS2103JAN2018-T11-B1/main[image:https://travis-ci.org/se-edu/addressbook-level4.svg?branch=master[Build Status]] +https://coveralls.io/github/CS2103JAN2018-T11-B1/main?branch=master[image:https://coveralls.io/repos/github/CS2103JAN2018-T11-B1/main/badge.svg?branch=master[Coverage Status]] +https://www.codacy.com/app/raymond511/main?utm_source=github.com&utm_medium=referral&utm_content=CS2103JAN2018-T11-B1/main&utm_campaign=Badge_Grade[image:https://api.codacy.com/project/badge/Grade/33b29d9ef2d3479c9b6bce03289421b1[Codacy Badge]] ifdef::env-github[] image::docs/images/Ui.png[width="600"] @@ -15,26 +13,26 @@ ifndef::env-github[] image::images/Ui.png[width="600"] endif::[] -* This is a desktop Address Book application. It has a GUI but most of the user interactions happen using a CLI (Command Line Interface). -* It is a Java sample application intended for students learning Software Engineering while using Java as the main programming language. -* It is *written in OOP fashion*. It provides a *reasonably well-written* code example that is *significantly bigger* (around 6 KLoC)than what students usually write in beginner-level SE modules. -* What's different from https://github.com/se-edu/addressbook-level3[level 3]: -** A more sophisticated GUI that includes a list panel and an in-built Browser. -** More test cases, including automated GUI testing. -** Support for _Build Automation_ using Gradle and for _Continuous Integration_ using Travis CI. +* This is a desktop application for private tutors. It has a GUI but most of the user interactions happen using a CLI (Command Line Interface). +* For Tutors: +. Provides easy management of task with the aid of a schedule. +. Has a calendar that makes viewing of task easier. +. Able to add, delete, edit any tuition or personal task into the schedule. +. Easily sort and group students by certain information. +. Has a log book that summarises the week for you. == Site Map * <> * <> -* <> * <> * <> == Acknowledgements -* Some parts of this sample application were inspired by the excellent http://code.makery.ch/library/javafx-8-tutorial/[Java FX tutorial] by +* Some parts of this application were inspired by the excellent http://code.makery.ch/library/javafx-8-tutorial/[Java FX tutorial] by _Marco Jakob_. -* Libraries used: https://github.com/TomasMikula/EasyBind[EasyBind], https://github.com/TestFX/TestFX[TextFX], https://bitbucket.org/controlsfx/controlsfx/[ControlsFX], https://github.com/FasterXML/jackson[Jackson], https://github.com/google/guava[Guava], https://github.com/junit-team/junit4[JUnit4] +* Libraries used: https://github.com/TomasMikula/EasyBind[EasyBind], https://github.com/TestFX/TestFX[TextFX], https://bitbucket.org/controlsfx/controlsfx/[ControlsFX], https://github.com/FasterXML/jackson[Jackson], https://github.com/google/guava[Guava], https://github.com/junit-team/junit4[JUnit4], https://github.com/dlemmermann/CalendarFX[CalendarFX] +* This application is a morphed of the AddressBook-Level4 project created by SE-EDU initiative at https://github.com/se-edu/ == Licence : link:LICENSE[MIT] diff --git a/build.gradle b/build.gradle index 50cd2ae52efc..39a3030adda9 100644 --- a/build.gradle +++ b/build.gradle @@ -45,7 +45,9 @@ dependencies { compile group: 'org.controlsfx', name: 'controlsfx', version: '8.40.11' compile group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.7.0' compile group: 'com.fasterxml.jackson.datatype', name: 'jackson-datatype-jsr310', version: '2.7.4' - compile group: 'com.google.guava', name: 'guava', version: '19.0' + compile group: 'com.google.guava', name: 'guava', version: '21.0' + compile fileTree(dir: 'lib', include: '*.jar') + testCompile group: 'junit', name: 'junit', version: '4.12' testCompile group: 'org.testfx', name: 'testfx-core', version: testFxVersion @@ -196,11 +198,6 @@ task deployOfflineDocs(type: Copy) { } } -task copyDummySearchPage(type: Copy) { - from 'docs/DummySearchPage.html' - into "${buildDir}/docs/html5" -} - deployOfflineDocs.dependsOn asciidoctor processResources.dependsOn deployOfflineDocs diff --git a/collated/functional/ChoChihTun.md b/collated/functional/ChoChihTun.md new file mode 100644 index 000000000000..60854abcbdad --- /dev/null +++ b/collated/functional/ChoChihTun.md @@ -0,0 +1,1714 @@ +# ChoChihTun +###### \java\seedu\address\logic\commands\AddPersonalTaskCommand.java +``` java + @Override + public CommandResult executeUndoableCommand() throws CommandException { + try { + model.addTask(toAdd); + } catch (TimingClashException tce) { + throw new CommandException(tce.getMessage()); + } + return new CommandResult(String.format(MESSAGE_SUCCESS, toAdd)); + } + +``` +###### \java\seedu\address\logic\commands\AddTuitionTaskCommand.java +``` java + @Override + public CommandResult executeUndoableCommand() throws CommandException { + try { + model.addTask(toAdd); + } catch (TimingClashException tce) { + throw new CommandException(tce.getMessage()); + } + return new CommandResult(MESSAGE_SUCCESS); + } + +``` +###### \java\seedu\address\logic\commands\AddTuteeCommand.java +``` java +/** + * Adds a tutee to the address book + */ +public class AddTuteeCommand extends UndoableCommand { + public static final String COMMAND_WORD = "addtutee"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds a tutee to the address book. " + + "Parameters: " + + PREFIX_NAME + "NAME " + + PREFIX_PHONE + "PHONE " + + PREFIX_EMAIL + "EMAIL " + + PREFIX_ADDRESS + "ADDRESS " + + PREFIX_SUBJECT + "SUBJECT " + + PREFIX_GRADE + "GRADE " + + PREFIX_EDUCATION_LEVEL + "EDUCATION LEVEL " + + PREFIX_SCHOOL + "SCHOOL " + + "[" + PREFIX_TAG + "TAG]...\n" + + "Example: " + COMMAND_WORD + " " + + PREFIX_NAME + "John Doe " + + PREFIX_PHONE + "98765432 " + + PREFIX_EMAIL + "johnd@example.com " + + PREFIX_ADDRESS + "311, Clementi Ave 2, #02-25 " + + PREFIX_SUBJECT + "Economics " + + PREFIX_GRADE + "B+ " + + PREFIX_EDUCATION_LEVEL + "junior college " + + PREFIX_SCHOOL + "Victoria Junior College " + + PREFIX_TAG + "priority " + + PREFIX_TAG + "owesMoney"; + + public static final String MESSAGE_SUCCESS = "New tutee added: %1$s"; + public static final String MESSAGE_DUPLICATE_PERSON = "This person already exists in the address book"; + + private final Tutee toAdd; + + /** + * Creates an AddTuteeCommand to add the specified {@code Tutee} + */ + public AddTuteeCommand(Tutee tutee) { + requireNonNull(tutee); + toAdd = tutee; + } + + @Override + public CommandResult executeUndoableCommand() throws CommandException { + requireNonNull(model); + try { + model.addPerson(toAdd); + return new CommandResult(String.format(MESSAGE_SUCCESS, toAdd)); + } catch (DuplicatePersonException dpe) { + throw new CommandException(MESSAGE_DUPLICATE_PERSON); + } + + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof AddTuteeCommand // instanceof handles nulls + && toAdd.equals(((AddTuteeCommand) other).toAdd)); + } +} + +``` +###### \java\seedu\address\logic\commands\ChangeCommand.java +``` java +/** + * Changes the Calendar Time Unit View of the Application + */ +public class ChangeCommand extends Command { + public static final String COMMAND_WORD = "change"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Changes the calendar view " + + "Parameters: " + + "TIME_UNIT (Only d, w, m or y) " + + "Example: " + COMMAND_WORD + " d"; + + public static final String MESSAGE_CONSTRAINT = "Time unit can only be d, w, m or y for day, week, month and year" + + " respectively"; + + public static final String MESSAGE_SUCCESS = "Calendar view changed"; + public static final String MESSAGE_SAME_VIEW = "Calendar is already in the requested view"; + private static final int INDEX_OF_TIME_UNIT = 0; + private static final String INITIAL_TIME_UNIT = "d"; + + private static String timeUnit = INITIAL_TIME_UNIT; + + /** + * Creates an ChangeCommand to change calendar into the specified view page time unit {@code timeUnit} + */ + public ChangeCommand(String timeUnit) { + requireNonNull(timeUnit); + this.timeUnit = timeUnit; + } + + public static String getTimeUnit() { + return timeUnit; + } + + @Override + public CommandResult execute() { + CalendarPanel.changeViewPage(timeUnit.charAt(INDEX_OF_TIME_UNIT)); + return new CommandResult(String.format(MESSAGE_SUCCESS)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof ChangeCommand // instanceof handles nulls + && timeUnit.equals(((ChangeCommand) other).timeUnit)); + } + +} +``` +###### \java\seedu\address\logic\commands\EditCommand.java +``` java + /** + * Checks if fields to be edited is valid for a person object + * + * @return true if fields to edit are valid for a person object + * false if fields to edit are invalid for a person object + */ + private boolean isEditPersonFieldValid() { + Tag tuteeTag = new Tag(TUTEE_TAG_NAME); + return !editPersonDescriptor.isAnyTuteeFieldEdited() + && isEditedPersonTagValid(tuteeTag); + } + + /** + * Checks if edited tag for person object is valid + * + * @param tuteeTag tutee tag is invalid for a person object + * @return true if edited tag is valid or tag is not being edited + * false if new tag is a tutee tag which is invalid for person + */ + private boolean isEditedPersonTagValid(Tag tuteeTag) { + if (editPersonDescriptor.isTagEdited()) { + return !editPersonDescriptor.tags.contains(tuteeTag); + } + return true; + } +``` +###### \java\seedu\address\logic\commands\EditCommand.java +``` java + if (personToEdit instanceof Tutee) { + Subject updatedSubject = editPersonDescriptor.getSubject().orElse(((Tutee) personToEdit).getSubject()); + Grade updatedGrade = editPersonDescriptor.getGrade().orElse(((Tutee) personToEdit).getGrade()); + EducationLevel updatedEducationalLevel = editPersonDescriptor.getEducationalLevel() + .orElse(((Tutee) personToEdit).getEducationLevel()); + School updatedSchool = editPersonDescriptor.getSchool().orElse(((Tutee) personToEdit).getSchool()); + + return new Tutee(updatedName, updatedPhone, updatedEmail, updatedAddress, updatedSubject, updatedGrade, + updatedEducationalLevel, updatedSchool, updatedTags); + } +``` +###### \java\seedu\address\logic\commands\EditCommand.java +``` java + public void setSubject(Subject subject) { + this.subject = subject; + } + + public Optional getSubject() { + return Optional.ofNullable(subject); + } + + public void setGrade(Grade grade) { + this.grade = grade; + } + + public Optional getGrade() { + return Optional.ofNullable(grade); + } + + public void setEducationLevel(EducationLevel educationLevel) { + this.educationLevel = educationLevel; + } + + public Optional getEducationalLevel() { + return Optional.ofNullable(educationLevel); + } + + public void setSchool(School school) { + this.school = school; + } + + public Optional getSchool() { + return Optional.ofNullable(school); + } +``` +###### \java\seedu\address\logic\commands\EditCommand.java +``` java + /** + * Checks if tag is being edited + * + * @return true if tag is being edited + * false if tag is not being edited + */ + public boolean isTagEdited() { + return tags != null; + } + + /** + * Checks if any tutee field is being edited + * + * @return true if no field is being edited + * false if at least 1 field is being edited + */ + public boolean isAnyTuteeFieldEdited() { + return subject != null + || grade != null + || educationLevel != null + || school != null; + } +``` +###### \java\seedu\address\logic\parser\AddTuteeCommandParser.java +``` java +/** + * Parses input arguments and creates a new AddTuteeCommand object + */ +public class AddTuteeCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the AddTuteeCommand + * and returns an AddTuteeCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public AddTuteeCommand parse(String args) throws ParseException { + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS, + PREFIX_SUBJECT, PREFIX_GRADE, PREFIX_EDUCATION_LEVEL, PREFIX_SCHOOL, PREFIX_TAG); + + if (!arePrefixesPresent(argMultimap, PREFIX_NAME, PREFIX_ADDRESS, PREFIX_PHONE, PREFIX_EMAIL, + PREFIX_SUBJECT, PREFIX_GRADE, PREFIX_EDUCATION_LEVEL, PREFIX_SCHOOL) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddTuteeCommand.MESSAGE_USAGE)); + } + + try { + Name name = ParserUtil.parseName(argMultimap.getValue(PREFIX_NAME)).get(); + Phone phone = ParserUtil.parsePhone(argMultimap.getValue(PREFIX_PHONE)).get(); + Email email = ParserUtil.parseEmail(argMultimap.getValue(PREFIX_EMAIL)).get(); + Address address = ParserUtil.parseAddress(argMultimap.getValue(PREFIX_ADDRESS)).get(); + Subject subject = ParserUtil.parseSubject(argMultimap.getValue(PREFIX_SUBJECT)).get(); + Grade grade = ParserUtil.parseGrade(argMultimap.getValue(PREFIX_GRADE)).get(); + EducationLevel educationLevel = ParserUtil.parseEducationLevel( + argMultimap.getValue(PREFIX_EDUCATION_LEVEL)).get(); + School school = ParserUtil.parseSchool(argMultimap.getValue(PREFIX_SCHOOL)).get(); + Set tagList = ParserUtil.parseTuteeTags(argMultimap.getAllValues(PREFIX_TAG)); + + Tutee person = new Tutee(name, phone, email, address, subject, grade, educationLevel, school, tagList); + + return new AddTuteeCommand(person); + } catch (IllegalValueException ive) { + throw new ParseException(ive.getMessage(), ive); + } + } + + /** + * 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()); + } +} +``` +###### \java\seedu\address\logic\parser\ChangeCommandParser.java +``` java +/** + * Parses input arguments and creates a new ChangeCommand object + */ +public class ChangeCommandParser implements Parser { + private static final String DAY = "d"; + private static final String WEEK = "w"; + private static final String MONTH = "m"; + private static final String YEAR = "y"; + + /** + * Parses the given {@code String} of arguments in the context of the ChangeCommand + * and returns an ChangeCommand object for execution. + * + * @throws ParseException if the user input does not conform the expected format + */ + public ChangeCommand parse(String args) throws ParseException { + try { + String timeUnit = ParserUtil.parseTimeUnit(args); + return new ChangeCommand(timeUnit); + } catch (IllegalValueException ive) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, ChangeCommand.MESSAGE_USAGE)); + } catch (SameTimeUnitException stue) { + throw new ParseException(stue.getMessage()); + } + } + + /** + * Checks if the user input view page time unit is valid + * + * @param trimmedTimeUnit to be checked + * @return true if view page time unit is valid + * false if the view page time unit is invalid + */ + public static boolean isValidTimeUnit(String trimmedTimeUnit) { + return (trimmedTimeUnit.equals(DAY) + || trimmedTimeUnit.equals(WEEK) + || trimmedTimeUnit.equals(MONTH) + || trimmedTimeUnit.equals(YEAR)); + } + + /** + * Checks if the new view page time unit clashes with the current time unit + * + * @param timeUnit to be checked + * @return true if the view page time unit clashes with the current time unit + * false if there is no clash + */ + public static boolean isTimeUnitClash(String timeUnit) { + String currentViewPage = ChangeCommand.getTimeUnit(); + return (timeUnit.equals(currentViewPage)); + } +} +``` +###### \java\seedu\address\logic\parser\exceptions\DurationParseException.java +``` java +/** + * Signals that the input duration format is invalid + */ +public class DurationParseException extends Exception { + public DurationParseException(String message) { + super(message); + } +} +``` +###### \java\seedu\address\logic\parser\exceptions\SameTimeUnitException.java +``` java +/** + * Signals that the input calendar view page time unit clashes with current time unit + */ +public class SameTimeUnitException extends Exception { + public SameTimeUnitException(String message) { + super(message); + } +} +``` +###### \java\seedu\address\logic\parser\ParserUtil.java +``` java + /** + * Parses a person's {@code Collection tags} into a {@code Set}. + */ + public static Set parsePersonTags(Collection tags) throws IllegalValueException { + requireNonNull(tags); + final Set tagSet = new HashSet<>(); + for (String tagName : tags) { + // a person should not have tutee tag + if (isTuteeTag(tagName)) { + throw new IllegalValueException(String.format(MESSAGE_INVALID_TAG, tagName)); + } + tagSet.add(parseTag(tagName)); + } + return tagSet; + } + + /** + * Parses a tutee's {@code Collection tags} into a {@code Set}. + */ + public static Set parseTuteeTags(Collection tags) throws IllegalValueException { + requireNonNull(tags); + final Set tagSet = new HashSet<>(); + for (String tagName : tags) { + // Tutee tag is added automatically by the Tutee constructor + if (!isTuteeTag(tagName)) { + tagSet.add(parseTag(tagName)); + } + } + return tagSet; + } + + /** + * Checks if {@code String tagName} is tutee tag name + * + * @param tagName to be checked + * @return true if tagName is tutee tag name + * false if tagName is not tutee tag name + */ + private static boolean isTuteeTag(String tagName) { + return tagName.toLowerCase().equals(TUTEE_TAG_NAME.toLowerCase()); + } + + /** + * Parses a {@code String subject} into an {@code Subject}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws IllegalValueException if the given {@code subject} is invalid. + */ + public static Subject parseSubject(String subject) throws IllegalValueException { + requireNonNull(subject); + String trimmedSubject = subject.trim(); + if (!Subject.isValidSubject(trimmedSubject)) { + throw new IllegalValueException(Subject.MESSAGE_SUBJECT_CONSTRAINTS); + } + return new Subject(trimmedSubject); + } + + /** + * Parses a {@code Optional subject} into an {@code Optional} if {@code subject} is present. + * See header comment of this class regarding the use of {@code Optional} parameters. + */ + public static Optional parseSubject(Optional subject) throws IllegalValueException { + requireNonNull(subject); + return subject.isPresent() ? Optional.of(parseSubject(subject.get())) : Optional.empty(); + } + + /** + * Parses a {@code String educationLevel} into an {@code EducationLevel}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws IllegalValueException if the given {@code educationLevel} is invalid. + */ + public static EducationLevel parseEducationLevel(String educationLevel) throws IllegalValueException { + requireNonNull(educationLevel); + String trimmedEducationLevel = educationLevel.trim(); + if (!EducationLevel.isValidEducationLevel(trimmedEducationLevel)) { + throw new IllegalValueException(EducationLevel.MESSAGE_EDUCATION_LEVEL_CONSTRAINTS); + } + return new EducationLevel(trimmedEducationLevel); + } + + /** + * Parses a {@code Optional educationLevel} into an {@code Optional} + * if {@code educationLevel} is present. + * See header comment of this class regarding the use of {@code Optional} parameters. + */ + public static Optional parseEducationLevel(Optional educationLevel) + throws IllegalValueException { + requireNonNull(educationLevel); + return educationLevel.isPresent() ? Optional.of(parseEducationLevel(educationLevel.get())) : Optional.empty(); + } + + /** + * Parses a {@code String school} into an {@code School}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws IllegalValueException if the given {@code school} is invalid. + */ + public static School parseSchool(String school) throws IllegalValueException { + requireNonNull(school); + String trimmedSchool = school.trim(); + if (!School.isValidSchool(trimmedSchool)) { + throw new IllegalValueException(School.MESSAGE_SCHOOL_CONSTRAINTS); + } + return new School(trimmedSchool); + } + + /** + * Parses a {@code Optional school} into an {@code Optional} if {@code school} is present. + * See header comment of this class regarding the use of {@code Optional} parameters. + */ + public static Optional parseSchool(Optional school) throws IllegalValueException { + requireNonNull(school); + return school.isPresent() ? Optional.of(parseSchool(school.get())) : Optional.empty(); + } + + /** + * Parses a {@code String grade} into an {@code Grade}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws IllegalValueException if the given {@code grade} is invalid. + */ + public static Grade parseGrade(String grade) throws IllegalValueException { + requireNonNull(grade); + String trimmedGrade = grade.trim(); + if (!Grade.isValidGrade(trimmedGrade)) { + throw new IllegalValueException(Grade.MESSAGE_GRADE_CONSTRAINTS); + } + return new Grade(trimmedGrade); + } + + /** + * Parses a {@code Optional grade} into an {@code Optional} if {@code grade} is present. + * See header comment of this class regarding the use of {@code Optional} parameters. + */ + public static Optional parseGrade(Optional grade) throws IllegalValueException { + requireNonNull(grade); + return grade.isPresent() ? Optional.of(parseGrade(grade.get())) : Optional.empty(); + } + + /** + * Parses a {@code String timeUnit} into an {@code String} and returns it. + * Leading and trailing whitespaces will be trimmed. + * + * @throws IllegalValueException if the given {@code timeUnit} is invalid. + */ + public static String parseTimeUnit(String timeUnit) throws IllegalValueException, SameTimeUnitException { + requireNonNull(timeUnit); + String trimmedTimeUnit = timeUnit.trim(); + if (!ChangeCommandParser.isValidTimeUnit(trimmedTimeUnit)) { + throw new IllegalValueException(ChangeCommand.MESSAGE_CONSTRAINT); + } + if (ChangeCommandParser.isTimeUnitClash(trimmedTimeUnit)) { + throw new SameTimeUnitException(ChangeCommand.MESSAGE_SAME_VIEW); + } + return trimmedTimeUnit; + } + +``` +###### \java\seedu\address\model\personal\PersonalTask.java +``` java +/** + * Represents the personal task that the user has + */ +public class PersonalTask implements Task { + + private static final String HOUR_DELIMITER = "h"; + private static final String MINUTE_DELIMITER = "m"; + private static final String NULL_STRING = ""; + + private DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd/MM/uuuu HH:mm") + .withResolverStyle(ResolverStyle.STRICT); + private String description; + private String duration; + private LocalDateTime taskDateTime; + private Entry entry; + + /** + * Creates a personal task + * + * @param taskDateTime date and time of the task + * @param duration duration of the task + * @param description description of the task + */ + public PersonalTask(LocalDateTime taskDateTime, String duration, String description) { + this.taskDateTime = taskDateTime; + this.duration = duration; + this.description = description; + this.entry = createCalendarEntry(); + } + + /** + * Creates an entry to be entered into the calendar + * + * @return Calendar entry + */ + private Entry createCalendarEntry() { + LocalDateTime endDateTime = getTaskEndTime(); + Interval interval = new Interval(taskDateTime, endDateTime); + Entry entry = new Entry(description); + entry.setInterval(interval); + return entry; + } + + /** + * Returns the end time of the task + */ + private LocalDateTime getTaskEndTime() { + int hoursInDuration = parseHours(); + int minutesInDuration = parseMinutes(); + LocalDateTime endDateTime = taskDateTime.plusHours(hoursInDuration).plusMinutes(minutesInDuration); + return endDateTime; + } + + /** + * Parses hour component out of duration + * + * @return number of hours in the duration + */ + private int parseHours() { + int indexOfHourDelimiter = duration.indexOf(HOUR_DELIMITER); + return Integer.parseInt(duration.substring(0, indexOfHourDelimiter)); + } + + /** + * Parses minute component out of duration + * + * @return number of minutes in the duration + */ + private int parseMinutes() { + int startOfMinutesIndex = duration.indexOf(HOUR_DELIMITER) + 1; + int indexOfMinuteDelimiter = duration.indexOf(MINUTE_DELIMITER); + return Integer.parseInt(duration.substring(startOfMinutesIndex, indexOfMinuteDelimiter)); + } + + public Entry getEntry() { + return entry; + } + + public LocalDateTime getTaskDateTime() { + return taskDateTime; + } + + public String getDescription() { + return description; + } + + public String getDuration() { + return duration; + } + + @Override + public String getStringTaskDateTime() { + return taskDateTime.format(formatter); + } + +``` +###### \java\seedu\address\model\task\exceptions\TimingClashException.java +``` java +/** + * Signals that there is a clash of timing in the schedule or there is a duplicate task + */ +public class TimingClashException extends DuplicateDataException { + public TimingClashException() { + super(MESSAGE_TASK_TIMING_CLASHES); + } +} +``` +###### \java\seedu\address\model\tutee\EducationLevel.java +``` java +/** + * Represents a Tutee's education level in the address book. + * Guarantees: immutable; is valid as declared in {@link #isValidEducationLevel(String)} + */ +public class EducationLevel { + + public static final String MESSAGE_EDUCATION_LEVEL_CONSTRAINTS = + "Education level should only be either primary, secondary or junior college, and it should not be blank"; + public static final String EDUCATION_LEVEL_VALIDATION_REGEX = "(?i)\\b(primary|secondary|(junior\\scollege))\\b"; + + public final String educationLevel; + + /** + * Constructs a {@code education level}. + * + * @param educationLevel A valid education level. + */ + public EducationLevel(String educationLevel) { + requireNonNull(educationLevel); + checkArgument(isValidEducationLevel(educationLevel), MESSAGE_EDUCATION_LEVEL_CONSTRAINTS); + this.educationLevel = educationLevel; + } + + /** + * Returns true if a given string is a valid education level. + */ + public static boolean isValidEducationLevel(String test) { + return test.matches(EDUCATION_LEVEL_VALIDATION_REGEX); + } + + + @Override + public String toString() { + return educationLevel; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof EducationLevel // instanceof handles nulls + && this.educationLevel.equals(((EducationLevel) other).educationLevel)); // state check + } + + @Override + public int hashCode() { + return educationLevel.hashCode(); + } +} +``` +###### \java\seedu\address\model\tutee\Grade.java +``` java +/** + * Represents a Tutee's subject grade + * Guarantees: immutable; is valid as declared in {@link #isValidGrade(String)} + */ +public class Grade { + + public static final String MESSAGE_GRADE_CONSTRAINTS = + "Grade should start with an alphabetic character and followed by any character (ONLY ONE) or blank, " + + "and it should not be blank"; + public static final String GRADE_VALIDATION_REGEX = "[\\p{Alpha}].??"; + + public final String grade; + + /** + * Constructs a {@code Grade}. + * + * @param grade A valid grade. + */ + public Grade(String grade) { + requireNonNull(grade); + checkArgument(isValidGrade(grade), MESSAGE_GRADE_CONSTRAINTS); + this.grade = grade; + } + + /** + * Returns true if a given string is a valid grade. + */ + public static boolean isValidGrade(String test) { + return test.matches(GRADE_VALIDATION_REGEX); + } + + + @Override + public String toString() { + return grade; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof Grade // instanceof handles nulls + && this.grade.equals(((Grade) other).grade)); // state check + } + + @Override + public int hashCode() { + return grade.hashCode(); + } +} +``` +###### \java\seedu\address\model\tutee\School.java +``` java +/** + * Represents a Tutee's school + * Guarantees: immutable; is valid as declared in {@link #isValidSchool(String)} + */ +public class School { + + public static final String MESSAGE_SCHOOL_CONSTRAINTS = + "School should only contain alphabetic characters and spaces, and it should not be blank"; + public static final String SCHOOL_VALIDATION_REGEX = "[\\p{Alpha}][\\p{Alpha} ]*"; + + public final String school; + + /** + * Constructs a {@code School}. + * + * @param school A valid school. + */ + public School(String school) { + requireNonNull(school); + checkArgument(isValidSchool(school), MESSAGE_SCHOOL_CONSTRAINTS); + this.school = school; + } + + /** + * Returns true if a given string is a valid school. + */ + public static boolean isValidSchool(String test) { + return test.matches(SCHOOL_VALIDATION_REGEX); + } + + + @Override + public String toString() { + return school; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof School // instanceof handles nulls + && this.school.equals(((School) other).school)); // state check + } + + @Override + public int hashCode() { + return school.hashCode(); + } +} +``` +###### \java\seedu\address\model\tutee\Subject.java +``` java +/** + * Represents a Tutee's subject + * Guarantees: immutable; is valid as declared in {@link #isValidSubject(String)} + */ +public class Subject { + + public static final String MESSAGE_SUBJECT_CONSTRAINTS = + "Subject should only contain alphabetic characters and spaces, and it should not be blank"; + public static final String SUBJECT_VALIDATION_REGEX = "[\\p{Alpha}][\\p{Alpha} ]*"; + + public final String subject; + + /** + * Constructs a {@code Subject}. + * + * @param subject A valid subject. + */ + public Subject(String subject) { + requireNonNull(subject); + checkArgument(isValidSubject(subject), MESSAGE_SUBJECT_CONSTRAINTS); + this.subject = subject; + } + + /** + * Returns true if a given string is a valid subject. + */ + public static boolean isValidSubject(String test) { + return test.matches(SUBJECT_VALIDATION_REGEX); + } + + + @Override + public String toString() { + return subject; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof Subject // instanceof handles nulls + && this.subject.equals(((Subject) other).subject)); // state check + } + + @Override + public int hashCode() { + return subject.hashCode(); + } +} +``` +###### \java\seedu\address\model\tutee\TuitionTask.java +``` java +/** + * Represents a tuition task that the tutee has + */ +public class TuitionTask implements Task { + + private static final String TUITION_TITLE = "Tuition with %1$s"; + private static final String HOUR_DELIMITER = "h"; + private static final String MINUTE_DELIMITER = "m"; + private static final String NULL_STRING = ""; + private DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd/MM/uuuu HH:mm") + .withResolverStyle(ResolverStyle.STRICT); + + private String tutee; + private String description; + private String duration; + private LocalDateTime taskDateTime; + private Entry entry; + + /** + * Creates a tuition task + * + * @param tutee tutee involved in the task + * @param taskDateTime date and time of the task + * @param duration duration of the task + * @param description description of the task + */ + public TuitionTask(String tutee, LocalDateTime taskDateTime, String duration, String description) { + this.tutee = tutee; + this.taskDateTime = taskDateTime; + this.duration = duration; + this.description = description; + this.entry = createCalendarEntry(); + } + + /** + * Creates an entry to be entered into the calendar + * + * @return Calendar entry + */ + private Entry createCalendarEntry() { + LocalDateTime endDateTime = getTaskEndTime(); + Interval interval = new Interval(taskDateTime, endDateTime); + Entry entry = new Entry(getTuitionTitle()); + entry.setInterval(interval); + return entry; + } + + /** + * Returns the end time of the task + */ + private LocalDateTime getTaskEndTime() { + int hoursInDuration = parseHours(); + int minutesInDuration = parseMinutes(); + LocalDateTime endDateTime = taskDateTime.plusHours(hoursInDuration).plusMinutes(minutesInDuration); + return endDateTime; + } + + /** + * Parses hour component out of duration + * + * @return number of hours in the duration + */ + private int parseHours() { + int indexOfHourDelimiter = duration.indexOf(HOUR_DELIMITER); + return Integer.parseInt(duration.substring(0, indexOfHourDelimiter)); + } + + /** + * Parses minute component out of duration + * + * @return number of minutes in the duration + */ + private int parseMinutes() { + int indexOfFirstMinuteDigit = duration.indexOf(HOUR_DELIMITER) + 1; + int indexOfMinuteDelimiter = duration.indexOf(MINUTE_DELIMITER); + return Integer.parseInt(duration.substring(indexOfFirstMinuteDigit, indexOfMinuteDelimiter)); + } + + public Entry getEntry() { + return entry; + } + + public LocalDateTime getTaskDateTime() { + return taskDateTime; + } + + public String getPerson() { + return tutee; + } + + public String getDescription() { + return description; + } + + public String getDuration() { + return duration; + } + + @Override + public String getStringTaskDateTime() { + return taskDateTime.format(formatter); + } + + public String getTuitionTitle() { + return String.format(TUITION_TITLE, tutee); + } + +``` +###### \java\seedu\address\model\tutee\Tutee.java +``` java +/** + * Represents a tutee in the address book. + * Guarantees: details are present and not null, field values are validated, immutable. + */ +public class Tutee extends Person { + private static final String TUTEE_TAG_NAME = "Tutee"; + + private Subject subject; + private Grade grade; + private EducationLevel educationLevel; + private School school; + + /** + * Every field must be present and not null. + */ + public Tutee(Name name, Phone phone, Email email, Address address, Subject subject, + Grade grade, EducationLevel educationLevel, School school, Set tags) { + super(name, phone, email, address, tags); + this.subject = subject; + this.grade = grade; + this.educationLevel = educationLevel; + this.school = school; + + // Creates a "Tutee" tag to represent a tutee + Tag tuteeTag = new Tag(TUTEE_TAG_NAME); + if (!this.tags.contains(tuteeTag)) { + try { + this.tags.add(tuteeTag); + } catch (UniqueTagList.DuplicateTagException e) { + // Should not have duplicate tutee tag + assert (false); + } + } + } + + public Subject getSubject() { + return subject; + } + + public Grade getGrade() { + return grade; + } + + public EducationLevel getEducationLevel() { + return educationLevel; + } + + public School getSchool() { + return school; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof Tutee)) { + return false; + } + + Tutee otherPerson = (Tutee) other; + return otherPerson.getName().equals(this.getName()) + && otherPerson.getPhone().equals(this.getPhone()) + && otherPerson.getEmail().equals(this.getEmail()) + && otherPerson.getAddress().equals(this.getAddress()) + && otherPerson.getEducationLevel().equals(this.getEducationLevel()) + && otherPerson.getGrade().equals(this.getGrade()) + && otherPerson.getSchool().equals(this.getSchool()) + && otherPerson.getSubject().equals(this.getSubject()); + } + + @Override + public int hashCode() { + // use this method for custom fields hashing instead of implementing your own + return Objects.hash(name, phone, email, address, subject, grade, educationLevel, school, tags); + } + + @Override + public String toString() { + final StringBuilder builder = new StringBuilder(); + builder.append(getName()) + .append(" Phone: ") + .append(getPhone()) + .append(" Email: ") + .append(getEmail()) + .append(" Address: ") + .append(getAddress()) + .append(" Subject: ") + .append(getSubject()) + .append(" Grade ") + .append(getGrade()) + .append(" Education Level: ") + .append(getEducationLevel()) + .append(" School: ") + .append(getSchool()) + .append(" Tags: "); + getTags().forEach(builder::append); + return builder.toString(); + } + +} +``` +###### \java\seedu\address\model\UniqueTaskList.java +``` java + /** + * Adds a task to the list. + * + * @throws TimingClashException if there is a clash in timing with an existing task + */ + public void add(Task toAdd) throws TimingClashException { + requireNonNull(toAdd); + if (isTimeClash(toAdd.getTaskDateTime(), toAdd.getDuration())) { + throw new TimingClashException(); + } + internalList.add(toAdd); + } + +``` +###### \java\seedu\address\model\UniqueTaskList.java +``` java + /** + * Checks for any clashes in the task timing in schedule + * + * @param startDateTime start date and time of new task + * @param duration duration of new task + */ + private boolean isTimeClash(LocalDateTime startDateTime, String duration) { + LocalDateTime taskEndTime = getTaskEndTime(duration, startDateTime); + + for (Task recordedTask : internalList) { + LocalDateTime startTimeOfRecordedTask = recordedTask.getTaskDateTime(); + String durationOfRecordedTask = recordedTask.getDuration(); + LocalDateTime endTimeOfRecordedTask = getTaskEndTime(durationOfRecordedTask, startTimeOfRecordedTask); + boolean isClash = !(taskEndTime.isBefore(startTimeOfRecordedTask) + || startDateTime.isAfter(endTimeOfRecordedTask)) + && !(taskEndTime.equals(startTimeOfRecordedTask) + || startDateTime.equals(endTimeOfRecordedTask)); + if (isClash) { + return true; + } + } + return false; + } + + /** + * Returns date and time when the task ends + */ + private static LocalDateTime getTaskEndTime(String duration, LocalDateTime startDateTime) { + int indexOfHourDelimiter = duration.indexOf(HOUR_DELIMITER); + int indexOfMinuteDelimiter = duration.indexOf(MINUTE_DELIMITER); + int indexOfFirstDigitInMinute = indexOfHourDelimiter + 1; + int hoursInDuration = Integer.parseInt(duration.substring(0, indexOfHourDelimiter)); + int minutesInDuration = Integer.parseInt(duration.substring(indexOfFirstDigitInMinute, indexOfMinuteDelimiter)); + + LocalDateTime taskEndTime; + taskEndTime = startDateTime.plusHours(hoursInDuration).plusMinutes(minutesInDuration); + return taskEndTime; + } + +``` +###### \java\seedu\address\ui\CalendarPanel.java +``` java +/** + * The Calendar Panel of the App. + */ +public class CalendarPanel extends UiPart { + + private static final String FXML = "CalendarPanel.fxml"; + private static final char DAY = 'd'; + private static final char WEEK = 'w'; + private static final char MONTH = 'm'; + private static final char YEAR = 'y'; + private static CalendarSource source = new CalendarSource("Schedule"); + private static Calendar calendar = new Calendar("Task"); + + @FXML + private static CalendarView calendarView = new CalendarView(); + + + public CalendarPanel() { + super(FXML); + calendarView.setRequestedTime(LocalTime.now()); + calendarView.setToday(LocalDate.now()); + calendarView.setTime(LocalTime.now()); + calendarView.setScaleX(0.95); + calendarView.setScaleY(1.15); + calendarView.setTranslateY(-40); + calendarView.showDayPage(); + disableViews(); + setupCalendar(); + } + + /** + * Initialises the calendar + */ + private void setupCalendar() { + source.getCalendars().add(calendar); + calendarView.getCalendarSources().add(source); + } + + /** + * Removes unnecessary buttons from interface + */ + private void disableViews() { + calendarView.setShowAddCalendarButton(false); + calendarView.setShowPrintButton(false); + calendarView.setShowPageToolBarControls(false); + calendarView.setShowSearchField(false); + } + + /** + * Changes the view page of the calendar + * @param timeUnit the view page time unit to be changed into + */ + public static void changeViewPage(char timeUnit) { + switch(timeUnit) { + case DAY: + calendarView.showDayPage(); + return; + case WEEK: + calendarView.showWeekPage(); + return; + case MONTH: + calendarView.showMonthPage(); + return; + case YEAR: + calendarView.showYearPage(); + return; + default: + // Should never enter here + assert (false); + } + } + + /** + * Updates the calendar with the updated list of tasks + * + * @param filteredTasks updated list of tasks + */ + public static void updateCalendar(List filteredTasks) { + if (isFilteredTaskListValid(filteredTasks)) { + Calendar updatedCalendar = new Calendar("task"); + for (Task task : filteredTasks) { + updatedCalendar.addEntry(task.getEntry()); + } + source.getCalendars().clear(); + source.getCalendars().add(updatedCalendar); + } else { + // Latest task list provided or loaded from storage should not have any task that clashes + assert (false); + } + } + + /** + * Checks if the given latest task list is valid + * + * @param taskList to be checked + * @return true if there is no clash between tasks so task list is valid + * false if there is clash between tasks so task list is invalid + */ + private static boolean isFilteredTaskListValid(List taskList) { + for (int i = 0; i < taskList.size(); i++) { + Entry taskEntryToBeChecked = taskList.get(i).getEntry(); + if (isTaskTimingClash(taskList, i, taskEntryToBeChecked)) { + return false; + } + } + return true; + } + + /** + * Checks if the given task clashes with any task in the list + * + * @param taskList list of tasks to check against + * @param index index of the given task + * @param taskEntryToBeChecked the given task entry + * @return true if given task does not clash with any task in the list + * false if given task clashes with another task in the list + */ + private static boolean isTaskTimingClash(List taskList, int index, Entry taskEntryToBeChecked) { + for (int j = index + 1; j < taskList.size(); j++) { + Entry taskEntryToCheckAgainst = taskList.get(j).getEntry(); + if (taskEntryToBeChecked.intersects(taskEntryToCheckAgainst)) { + return true; + } + } + return false; + } + + @Override + public CalendarView getRoot() { + return calendarView; + } + +} +``` +###### \resources\view\CalendarPanel.fxml +``` fxml + + + + + +``` +###### \resources\view\MainWindow.fxml +``` fxml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +``` +###### \resources\view\TuitionConnectTheme.css +``` css +.background { + -fx-background-color: white; + background-color: white; /* Used in the default.html file */ +} + +.label { + -fx-font-size: 10pt; + -fx-font-family: "Andale Mono"; + -fx-text-fill: black; + -fx-opacity: 0.9; +} + +.label-bright { + -fx-font-size: 10pt; + -fx-font-family: "Andale Mono"; + -fx-text-fill: black; + -fx-opacity: 1; +} + +.label-header { + -fx-font-size: 30pt; + -fx-font-family: "Andale Mono"; + -fx-text-fill: black; + -fx-opacity: 1; +} + +.text-field { + -fx-font-size: 12pt; + -fx-font-family: "Andale Mono"; +} + +.tab-pane { + -fx-padding: 0 0 0 1; +} + +.tab-pane .tab-header-area { + -fx-padding: 0 0 0 0; + -fx-min-height: 0; + -fx-max-height: 0; +} + +.table-view { + -fx-base: black; + -fx-control-inner-background: black; + -fx-background-color: black; + -fx-table-cell-border-color: transparent; + -fx-table-header-border-color: transparent; + -fx-padding: 5; +} + +.table-view .column-header-background { + -fx-background-color: transparent; +} + +.table-view .column-header, .table-view .filler { + -fx-size: 35; + -fx-border-width: 0 0 1 0; + -fx-background-color: transparent; + -fx-border-color: + transparent + transparent + derive(-fx-base, 80%) + transparent; + -fx-border-insets: 0 10 1 0; +} + +.table-view .column-header .label { + -fx-font-size: 20pt; + -fx-font-family: "Andale Mono"; + -fx-text-fill: white; + -fx-alignment: center-left; + -fx-opacity: 1; +} + +.table-view:focused .table-row-cell:filled:focused:selected { + -fx-background-color: -fx-focus-color; +} + +.split-pane:horizontal .split-pane-divider { + -fx-background-color: white; + -fx-border-color: transparent transparent transparent white; +} + +.split-pane { + -fx-background-color: white; +} + +.list-view { + -fx-background-insets: 0; + -fx-padding: 0; + -fx-background-color: derive(#1d1d1d, 20%); +} + +.list-cell { + -fx-label-padding: 0 0 0 0; + -fx-graphic-text-gap : 0; + -fx-padding: 0 0 0 0; +} + +.list-cell:filled:even { + -fx-background-color: #c9d8ef; +} + +.list-cell:filled:odd { + -fx-background-color: #c9d8ef; +} + +.list-cell:filled:selected { + -fx-background-color: #1f3351; +} + +.list-cell:filled:selected #cardPane { + -fx-border-color: black; + -fx-border-width: 3px; +} + +.list-cell .label { + -fx-text-fill: white; +} + +.cell_big_label { /* Name */ + -fx-font-family: "Segoe UI Semibold"; + -fx-font-size: 16px; + -fx-text-fill: white; +} + +.cell_small_label { /* Details */ + -fx-font-family: "Segoe UI"; + -fx-font-size: 13px; + -fx-text-fill: white; +} + +.anchor-pane { + -fx-background-color: #c9bbbb; +} + +.pane-with-border { + -fx-background-color: black; + -fx-border-color: transparent; + -fx-border-top-width: 1px; +} + +.status-bar { + -fx-background-color: white; + -fx-text-fill: black; +} + +.result-display { /* Command result */ + -fx-background-color: transparent; + -fx-font-family: "Andale Mono"; + -fx-font-size: 13pt; + -fx-text-fill: black; +} + +.result-display .label { + -fx-text-fill: black !important; +} + +.status-bar .label { + -fx-font-family: "Segoe UI Light"; + -fx-text-fill: white; +} + +.status-bar-with-border { + -fx-background-color: derive(#1d1d1d, 30%); + -fx-border-color: derive(#1d1d1d, 25%); + -fx-border-width: 1px; +} + +.status-bar-with-border .label { + -fx-text-fill: white; +} + +.grid-pane { + -fx-background-color: white; + -fx-border-color: black; + -fx-border-width: 1px; +} + +.grid-pane .anchor-pane { + -fx-background-color: #113756; +} + +.context-menu { + -fx-background-color: #22529e; +} + +.context-menu .label { + -fx-text-fill: white; +} + +.menu-bar { + -fx-background-color: #113756; +} + +.menu-bar .label { + -fx-font-size: 14pt; + -fx-font-family: "Andale Mono"; + -fx-text-fill: white; + -fx-opacity: 0.9; +} + +.menu .left-container { + -fx-background-color: black; +} + +/* + * Metro style Push Button + * Author: Pedro Duque Vieira + * http://pixelduke.wordpress.com/2012/10/23/jmetro-windows-8-controls-on-java/ + */ +.button { + -fx-padding: 5 22 5 22; + -fx-border-color: #e2e2e2; + -fx-border-width: 2; + -fx-background-radius: 0; + -fx-background-color: #1d1d1d; + -fx-font-family: "Andale Mono", Helvetica, Arial, sans-serif; + -fx-font-size: 11pt; + -fx-text-fill: black; + -fx-background-insets: 0 0 0 0, 0, 1, 2; +} + +.button:hover { + -fx-background-color: #3a3a3a; +} + +.button:pressed, .button:default:hover:pressed { + -fx-background-color: white; + -fx-text-fill: black; +} + +.button:focused { + -fx-border-color: white, white; + -fx-border-width: 1, 1; + -fx-border-style: solid, segments(1, 1); + -fx-border-radius: 0, 0; + -fx-border-insets: 1 1 1 1, 0; +} + +.button:disabled, .button:default:disabled { + -fx-opacity: 0.4; + -fx-background-color: #1d1d1d; + -fx-text-fill: black; +} + +.button:default { + -fx-background-color: -fx-focus-color; + -fx-text-fill: black; +} + +.button:default:hover { + -fx-background-color: black; +} + +.dialog-pane { + -fx-background-color: black; +} + +.dialog-pane > *.button-bar > *.container { + -fx-background-color: black; +} + +.dialog-pane > *.label.content { + -fx-font-size: 14px; + -fx-font-weight: bold; + -fx-text-fill: white; +} + +.dialog-pane:header *.header-panel { + -fx-background-color: black; +} + +.dialog-pane:header *.header-panel *.label { + -fx-font-size: 18px; + -fx-font-style: italic; + -fx-fill: white; + -fx-text-fill: white; +} + +.scroll-bar { /* Scroll bar column background color */ + -fx-background-color: #ced8dd; +} + +.scroll-bar .thumb { /* Scroll bar background color */ + -fx-background-color: #939a9e; + -fx-background-insets: 3; +} + +.scroll-bar .increment-button, .scroll-bar .decrement-button { + -fx-background-color: transparent; + -fx-padding: 0 0 0 0; +} + +.scroll-bar .increment-arrow, .scroll-bar .decrement-arrow { + -fx-shape: " "; +} + +.scroll-bar:vertical .increment-arrow, .scroll-bar:vertical .decrement-arrow { + -fx-padding: 1 8 1 8; +} + +.scroll-bar:horizontal .increment-arrow, .scroll-bar:horizontal .decrement-arrow { + -fx-padding: 8 1 8 1; +} + +#cardPane { + -fx-background-color: #37598e; + -fx-border-width: 0; +} + +#commandTypeLabel { + -fx-font-size: 11px; + -fx-text-fill: black; +} + +#commandTextField { /* Command box */ + -fx-background-color: transparent #383838 transparent #383838; + -fx-background-insets: 0; + -fx-border-color: black; + -fx-border-insets: 0; + -fx-border-width: 2.1; + -fx-font-family: "Andale Mono"; + -fx-font-size: 13pt; + -fx-text-fill: black; + -fx-prompt-text-fill: black; +} + +#filterField, #personListPanel, #personWebpage { + -fx-effect: innershadow(gaussian, black, 10, 0, 0, 0); +} + +#resultDisplay .content { + -fx-background-color: #c9bbbb; + -fx-background-radius: 0; +} + +#tags { + -fx-hgap: 7; + -fx-vgap: 3; +} + +#tags .label { + -fx-text-fill: white; + -fx-background-color: gray; + -fx-padding: 1 3 1 3; + -fx-border-radius: 2; + -fx-background-radius: 15; + -fx-font-size: 11; +} + +#calendarPlaceholder { + -fx-background-color: white; +} + +#commandBoxPlaceholder { + -fx-background-color: white; +} + +#resultDisplayPlaceholder { + -fx-background-color: white; +} + +#statusbarPlaceholder { + -fx-background-color: white; +} + +#personListPanelPlaceholder { + -fx-background-color: white; +} + +#taskListPanelPlaceholder { + -fx-background-color: white; +} +``` diff --git a/collated/functional/a-shakra.md b/collated/functional/a-shakra.md new file mode 100644 index 000000000000..4df6043859a6 --- /dev/null +++ b/collated/functional/a-shakra.md @@ -0,0 +1,614 @@ +# a-shakra +###### \java\seedu\address\logic\commands\ListTaskCommand.java +``` java + +public class ListTaskCommand extends Command { + + public static final String COMMAND_WORD = "listtask"; + public static final String COMMAND_ALIAS = "lt"; + + public static final String MESSAGE_SUCCESS = "Listed all tasks"; + + + @Override + public CommandResult execute() { + model.updateFilteredTaskList(PREDICATE_SHOW_ALL_TASKS); + return new CommandResult(MESSAGE_SUCCESS); + } + +} +``` +###### \java\seedu\address\logic\LogicManager.java +``` java + @Override + public ObservableList getFilteredTaskList() { + return model.getFilteredTaskList(); + } +``` +###### \java\seedu\address\model\AddressBook.java +``` java + public void setTasks(List tasks) throws TimingClashException { + this.tasks.setTasks(tasks); + } +``` +###### \java\seedu\address\model\AddressBook.java +``` java + /** + * Adds a task to the address book. + * + */ + public void addTask(Task t) throws TimingClashException { + tasks.add(t); + } + +``` +###### \java\seedu\address\model\AddressBook.java +``` java + /** + * Removes a task from the address book. + * + */ + public boolean removeTask(Task key) throws TaskNotFoundException { + if (tasks.remove(key)) { + return true; + } else { + System.out.println("Didn't work"); + return false; + } + } +``` +###### \java\seedu\address\model\ModelManager.java +``` java + @Override + public synchronized void addTask(Task aTask) throws TimingClashException { + addressBook.addTask(aTask); + updateFilteredTaskList(PREDICATE_SHOW_ALL_TASKS); //Change to new predicate? + indicateAddressBookChanged(); + } + + @Override + public synchronized void deleteTask(Task target) throws TaskNotFoundException { + addressBook.removeTask(target); + indicateAddressBookChanged(); + } + + @Override + public ObservableList getFilteredTaskList() { + return FXCollections.unmodifiableObservableList(sortedTasks); + } + + @Override + public void updateFilteredTaskList(Predicate predicate) { + requireNonNull(predicate); + filteredTasks.setPredicate(predicate); + } +``` +###### \java\seedu\address\model\ReadOnlyAddressBook.java +``` java + /** + * Returns an unmodifiable view of the tasks list + * This list will not contain any duplicate task + */ + ObservableList getTaskList(); +``` +###### \java\seedu\address\model\TaskContainsKeywordsPredicate.java +``` java +public class TaskContainsKeywordsPredicate implements Predicate { + private final List keywords; + + public TaskContainsKeywordsPredicate(List keywords) { + this.keywords = keywords; + } + + @Override + public boolean test(Task task) { + return keywords.stream() + .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(task.getDescription(), keyword)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof TaskContainsKeywordsPredicate // instanceof handles nulls + && this.keywords.equals(((TaskContainsKeywordsPredicate) other).keywords)); // state check + } + +} +``` +###### \java\seedu\address\model\UniqueTaskList.java +``` java +public class UniqueTaskList implements Iterable { + + private static final String HOUR_DELIMITER = "h"; + private static final String MINUTE_DELIMITER = "m"; + private final ObservableList internalList = FXCollections.observableArrayList(); + + /** + * Constructs empty TaskList. + */ + public UniqueTaskList() {} + +``` +###### \java\seedu\address\model\UniqueTaskList.java +``` java + /** + * Replaces the person {@code target} in the list with {@code editedPerson}. + */ + public void setTask(Task target, Task editedTask) throws TaskNotFoundException { + requireNonNull(editedTask); + + int index = internalList.indexOf(target); + if (index == -1) { + throw new TaskNotFoundException(); + } + internalList.set(index, editedTask); + } + + + /** + * Removes the equivalent task from the list. + */ + public boolean remove(Task toRemove) throws TaskNotFoundException { + requireNonNull(toRemove); + final boolean taskFoundAndDeleted = internalList.remove(toRemove); + if (!taskFoundAndDeleted) { + throw new TaskNotFoundException(); + } + return taskFoundAndDeleted; + } + + public void setTasks(UniqueTaskList replacement) { + this.internalList.setAll(replacement.internalList); + } + + + public void setTasks(List tasks) throws TimingClashException { + requireAllNonNull(tasks); + final UniqueTaskList replacement = new UniqueTaskList(); + for (final Task task : tasks) { + replacement.add(task); + } + setTasks(replacement); + } + + /** + * Returns the backing list as an unmodifiable {@code ObservableList}. + */ + public ObservableList asObservableList() { + return FXCollections.unmodifiableObservableList(internalList); + } + +``` +###### \java\seedu\address\model\UniqueTaskList.java +``` java + @Override + public Iterator iterator() { + return internalList.iterator(); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof UniqueTaskList // instanceof handles nulls + && this.internalList.equals(((UniqueTaskList) other).internalList)); + } + + @Override + public int hashCode() { + return internalList.hashCode(); + } +} +``` +###### \java\seedu\address\model\util\SampleDataUtil.java +``` java +/** + * Contains utility methods for populating {@code AddressBook} with sample data. + */ +public class SampleDataUtil { + public static final String DATETIME1 = "16/04/2018 15:15"; + public static final String DATETIME2 = "19/04/2018 09:25"; + public static final String DATETIME3 = "07/06/2018 16:45"; + public static final String DATETIME4 = "03/06/2019 12:10"; + public static final String DATETIME5 = "05/07/2020 18:45"; + public static final String DATETIME6 = "15/07/2018 06:55"; + public static final String DATETIME7 = "20/10/2018 11:11"; + public static final String DATETIME8 = "16/12/2018 08:18"; + public static final String DATETIME9 = "23/12/2018 10:28"; + public static final String DATETIME10 = "13/01/2018 11:30"; + public static final String DATETIME11 = "29/01/2018 12:30"; + private static DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd/MM/uuuu HH:mm") + .withResolverStyle(ResolverStyle.STRICT); + + public static Person[] getSamplePersons() { + return new Person[] { + 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")), + 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")), + 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")), + new Tutee(new Name("Anas Shakra"), new Phone("514552256"), new Email("shakra.a@hotmail.com"), + new Address("590 Souart"), new Subject("Chemistry"), new Grade("A"), + new EducationLevel("secondary"), new School("NUS"), getTagSet("family")), + new Tutee(new Name("Irfan Ibrahim"), new Phone("92492021"), new Email("irfan@example.co"), + new Address("Blk 47 Tampines Street 20"), new Subject("Chemistry"), new Grade("C"), + new EducationLevel("secondary"), new School("NUS"), getTagSet("family")), + new Tutee(new Name("Roy Balakrishnan"), new Phone("92624417"), new Email("royb@example.com"), + new Address("Blk 45 Aljunied Street"), new Subject("Physics"), new Grade("A"), + new EducationLevel("secondary"), new School("NUS"), getTagSet("family")), + new Tutee(new Name("David Li"), new Phone("91031282"), new Email("lidavid@example.com"), + new Address("Blk 436 Serangoon Gardens Street"), new Subject("Math"), new Grade("C"), + new EducationLevel("primary"), new School("NUS"), getTagSet("friend")), + }; + } + public static Task[] getSampleTasks() { + return new Task[]{ + new PersonalTask(LocalDateTime.parse(DATETIME1, formatter), "2h15m", "exampleTask1"), + new TuitionTask("Anas Shakra", LocalDateTime.parse(DATETIME2, formatter), "5h25m", "exampleTask2"), + new PersonalTask(LocalDateTime.parse(DATETIME3, formatter), "3h45m", "exampleTask3"), + new PersonalTask(LocalDateTime.parse(DATETIME4, formatter), "12h10m", "exampleTask4"), + new PersonalTask(LocalDateTime.parse(DATETIME5, formatter), "02h45m", "exampleTask5"), + new PersonalTask(LocalDateTime.parse(DATETIME6, formatter), "06h55m", "exampleTask6"), + new PersonalTask(LocalDateTime.parse(DATETIME7, formatter), "03h11m", "exampleTask7"), + new PersonalTask(LocalDateTime.parse(DATETIME8, formatter), "08h18m", "exampleTask8"), + new TuitionTask("Roy Balakrishnan", LocalDateTime.parse(DATETIME9, formatter), "10h28m", + "exampleTask9"), + new TuitionTask("Irfan Ibrahim", LocalDateTime.parse(DATETIME10, formatter), "11h30m", + "exampleTask10"), + new TuitionTask("David Li", LocalDateTime.parse(DATETIME11, formatter), "12h30m", + "exampleTask11"), + }; + } + + public static ReadOnlyAddressBook getSampleAddressBook() { + try { + AddressBook sampleAb = new AddressBook(); + for (Person samplePerson : getSamplePersons()) { + sampleAb.addPerson(samplePerson); + } + for (Task sampleTask : getSampleTasks()) { + sampleAb.addTask(sampleTask); + } + return sampleAb; + } catch (DuplicatePersonException e) { + throw new AssertionError("sample data cannot contain duplicate persons", e); + } catch (TimingClashException tce) { + throw new AssertionError("sample data cannot contain duplicate Tasks, tce"); + } + } +``` +###### \java\seedu\address\storage\XmlAdaptedTask.java +``` java +public class XmlAdaptedTask { + + public static final String MISSING_FIELD_MESSAGE_FORMAT = "Task's %s field is missing!"; + + @XmlElement(required = true) + private String name; + @XmlElement(required = true) + private String description; + @XmlElement(required = true) + private String duration; + @XmlElement(required = true) + private String dateAndTime; + + /** + * Constructs an XmlAdaptedTask. + * This is the no-arg constructor that is required by JAXB. + */ + public XmlAdaptedTask() {} + + /** + * Constructs an {@code XmlAdaptedTask} with given personal task details. + */ + public XmlAdaptedTask(String description, String duration, String dateAndTime) { + //this.name = "null"; + this.description = description; + this.duration = duration; + this.dateAndTime = dateAndTime; + } + + public XmlAdaptedTask(String name, String description, String duration, String dateAndTime) { + this.name = name; + this.description = description; + this.duration = duration; + this.dateAndTime = dateAndTime; + } + + /** + * Converts a given Task into this class for JAXB use. + * + */ + public XmlAdaptedTask(Task source) { + description = source.getDescription(); + duration = source.getDuration(); + dateAndTime = source.getTaskDateTime().toString(); + if (source instanceof TuitionTask) { + name = ((TuitionTask) source).getPerson(); + } + } + + /** + * Converts this jaxb-friendly adapted task object into the model's Task object. + * + * @throws IllegalValueException if there were any data constraints violated in the adapted person + * Because of the way Task was designed (As an interface), i'm forced to just input this as a PersonalTask + * until a better solution can be found + */ + + public Task toModelType() throws IllegalValueException { + LocalDateTime taskDateTime = LocalDateTime.parse(dateAndTime); + if (this.description == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, + Task.MESSAGE_DESCRIPTION_CONSTRAINTS)); + } + if (this.duration == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, + Task.MESSAGE_DURATION_CONSTRAINTS)); + } + if (this.dateAndTime == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, + Task.MESSAGE_DATETIME_CONSTRAINTS)); + } + if (this.name == null) { + return new PersonalTask(taskDateTime, duration, description); + } else { + return new TuitionTask(name, taskDateTime, duration, description); + } + } + + /** + * Returns true if the two tasks are equal. Needs to be updated to reflect the name parameter + */ + public boolean equals(Object other) { + + if (other == this) { + return true; + } + + if (!(other instanceof XmlAdaptedTask)) { + return false; + } + + XmlAdaptedTask otherTask = (XmlAdaptedTask) other; + return Objects.equals(description, otherTask.description) + && Objects.equals(duration, otherTask.duration) + && Objects.equals(dateAndTime, otherTask.dateAndTime) + && Objects.equals(name, otherTask.name); + } +} +``` +###### \java\seedu\address\storage\XmlSerializableAddressBook.java +``` java +@XmlRootElement(name = "addressbook") +public class XmlSerializableAddressBook { + + @XmlElement + private List persons; + @XmlElement + private List tags; + @XmlElement + private List tasks; + + /** + * Creates an empty XmlSerializableAddressBook. + * This empty constructor is required for marshalling. + */ + public XmlSerializableAddressBook() { + persons = new ArrayList<>(); + tags = new ArrayList<>(); + tasks = new ArrayList<>(); + } + + /** + * Conversion + */ + public XmlSerializableAddressBook(ReadOnlyAddressBook src) { + this(); + persons.addAll(src.getPersonList().stream().map(XmlAdaptedPerson::new).collect(Collectors.toList())); + tags.addAll(src.getTagList().stream().map(XmlAdaptedTag::new).collect(Collectors.toList())); + tasks.addAll(src.getTaskList().stream().map(XmlAdaptedTask::new).collect(Collectors.toList())); + } + + /** + * Converts this addressbook into the model's {@code AddressBook} object. + * + * @throws IllegalValueException if there were any data constraints violated or duplicates in the + * {@code XmlAdaptedPerson} or {@code XmlAdaptedTag}. + */ + public AddressBook toModelType() throws IllegalValueException { + AddressBook addressBook = new AddressBook(); + for (XmlAdaptedTag t : tags) { + addressBook.addTag(t.toModelType()); + } + for (XmlAdaptedPerson p : persons) { + addressBook.addPerson(p.toModelType()); + } + for (XmlAdaptedTask t: tasks) { + addressBook.addTask(t.toModelType()); + } + return addressBook; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof XmlSerializableAddressBook)) { + return false; + } + + XmlSerializableAddressBook otherAb = (XmlSerializableAddressBook) other; + // The tasks condition might be a problem because of the design of tasks + return persons.equals(otherAb.persons) && tags.equals(otherAb.tags) && tasks.equals(otherAb.tasks); + } +} +``` +###### \java\seedu\address\ui\TaskCard.java +``` java +public class TaskCard extends UiPart { + private static final String FXML = "TaskListCard.fxml"; + public final Task task; + private DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd/MM/uuuu HH:mm") + .withResolverStyle(ResolverStyle.STRICT); + + /** + * Note: Certain keywords such as "location" and "resources" are reserved keywords in JavaFX. + * As a consequence, UI elements' variable names cannot be set to such keywords + * or an exception will be thrown by JavaFX during runtime. + * + * @see The issue on AddressBook level 4 + */ + + @FXML + private HBox cardPane; + @FXML + private Label id; + @FXML + private Label taskDateAndTime; + @FXML + private Label duration; + @FXML + private Label description; + + public TaskCard(Task task, int displayedIndex) { + super(FXML); + this.task = task; + id.setText(displayedIndex + ". "); + if (task instanceof TuitionTask) { + description.setText(((TuitionTask) task).getTuitionTitle()); + } else { + description.setText(task.getDescription()); + } + duration.setText(task.getDuration()); + taskDateAndTime.setText(task.getTaskDateTime().format(formatter)); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof TaskCard)) { + return false; + } + + // state check + TaskCard card = (TaskCard) other; + return id.getText().equals(card.id.getText()) + && task.equals(card.task); + } + +} + +``` +###### \java\seedu\address\ui\TaskCardListPanel.java +``` java +public class TaskCardListPanel extends UiPart { + private static final String FXML = "TaskCardListPanel.fxml"; + private final Logger logger = LogsCenter.getLogger(TaskCardListPanel.class); + + @FXML + private ListView taskListView; + + public TaskCardListPanel(ObservableList taskList) { + super(FXML); + setConnections(taskList); + registerAsAnEventHandler(this); + } + + private void setConnections(ObservableList taskList) { + ObservableList mappedList = EasyBind.map( + taskList, (task) -> new TaskCard(task, taskList.indexOf(task) + 1)); + taskListView.setItems(mappedList); + taskListView.setCellFactory(listView -> new TaskListViewCell()); + setEventHandlerForSelectionChangeEvent(); + } + + + private void setEventHandlerForSelectionChangeEvent() { + taskListView.getSelectionModel().selectedItemProperty() + .addListener((observable, oldValue, newValue) -> { + if (newValue != null) { + logger.fine("Selection in Task list panel changed to : '" + newValue + "'"); + raise(new TaskPanelSelectionChangedEvent(newValue)); + } + }); + } + + /** + * Scrolls to the {@code PersonCard} at the {@code index} and selects it. + */ + private void scrollTo(int index) { + Platform.runLater(() -> { + taskListView.scrollTo(index); + taskListView.getSelectionModel().clearAndSelect(index); + }); + } + + @Subscribe + private void handleJumpToListRequestEvent(JumpToListRequestEvent event) { + logger.info(LogsCenter.getEventHandlingLogMessage(event)); + scrollTo(event.targetIndex); + } + + /** + * Custom {@code ListCell} that displays the graphics of a {@code PersonCard}. + */ + class TaskListViewCell extends ListCell { + + @Override + protected void updateItem(TaskCard task, boolean empty) { + super.updateItem(task, empty); + + if (empty || task == null) { + setGraphic(null); + setText(null); + } else { + setGraphic(task.getRoot()); + } + } + } + +} +``` +###### \resources\view\TaskCardListPanel.fxml +``` fxml + + + +``` +###### \resources\view\TaskListCard.fxml +``` fxml + + + + + + + + + + + + + + + +``` diff --git a/collated/functional/raymond511.md b/collated/functional/raymond511.md new file mode 100644 index 000000000000..895b0cf27873 --- /dev/null +++ b/collated/functional/raymond511.md @@ -0,0 +1,184 @@ +package seedu.address.commons.util; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.security.*; +import java.util.logging.Logger; + +import javax.crypto.BadPaddingException; +import javax.crypto.Cipher; + +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.SecretKey; + +import javax.crypto.spec.SecretKeySpec; + +import seedu.address.commons.core.LogsCenter; + +/** + * A Class that encrypts and decrypts XML files stored on the hard disk. + * + */ +//@@author raymond511 +public class EncryptionUtil { + + /** + *The standard version of the JRE/JDK are under export restrictions. + *That also includes that some cryptographic algorithms are not allowed to be shipped in the standard version. + *Replace files in library with Java Cryptography Extension (JCE) Unlimited Strength Jurisdiction Policy Files 8 + */ + + private static final Logger logger = LogsCenter.getLogger(EncryptionUtil.class); + private static final String passwordToHash = "password"; + private static byte[] salt = new byte[0]; + private static final String password = getSecurePassword(passwordToHash, salt); + + /** + * Adds salt to password cryptography + * @throws NoSuchAlgorithmException if salt acnnot be generated + * @throws NoSuchProviderException if salt cannot be generated + */ + + private static byte[] getSalt() throws NoSuchAlgorithmException, NoSuchProviderException { + try { + SecureRandom sr = SecureRandom.getInstance("SHA1PRNG", "SUN"); + byte[] salt = new byte[16]; + sr.nextBytes(salt); + } catch (NoSuchAlgorithmException e) { + logger.severe("This algorithm is not supported " + e.getMessage()); + } catch (NoSuchProviderException e) { + logger.severe("The provider is not available " + e.getMessage()); + } + return salt; + } + + /** + * Generates a secure password + * + * @param passwordToHash used to generate a new password + * @param salt to adds security to the new password + * @throws NoSuchAlgorithmException if new password cannot be generated + */ + + private static String getSecurePassword(String passwordToHash, byte[] salt) { + String generatedPassword = null; + try { + MessageDigest md = MessageDigest.getInstance("MD5"); + md.update(salt); + byte[] bytes = md.digest(passwordToHash.getBytes()); + StringBuilder sb = new StringBuilder(); + for(int i=0; i< bytes.length ;i++) { + sb.append(Integer.toString((bytes[i] & 0xff) + 0x100, 16).substring(1)); } + generatedPassword = sb.toString(); + } catch (NoSuchAlgorithmException e) { + logger.severe("This algorithm is not supported " + e.getMessage()); + } + return generatedPassword; + } + + /** + * Encrypts an XML file + * + * @param file path of the file to be encrypted + * @throws IOException if file could not be found + */ + public static void encrypt(File file) throws IOException { + try { + Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding"); + SecretKey privateKey = generateKey(); + cipher.init(Cipher.ENCRYPT_MODE, privateKey); + fileToBytes(cipher, file); + } catch (GeneralSecurityException gse) { + logger.severe("Cipher or Padding might not be supported " + gse.getMessage()); + } catch (UnsupportedEncodingException use) { + logger.info("Encoding Unsupported " + use.getMessage()); + } + + } + + /** + * Decrypts XML file + * + * @param file path of the file to be decrypted + * @throws IOException if file could not be found + */ + public static void decrypt(File file) throws IOException { + + try { + Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding"); + SecretKey privateKey = generateKey(); + cipher.init(Cipher.DECRYPT_MODE, privateKey); + fileToBytes(cipher, file); + } catch (GeneralSecurityException gse) { + logger.severe("Cipher or Padding might not be supported " + gse.getMessage()); + } catch (UnsupportedEncodingException use) { + logger.info("Encoding Unsupported " + use.getMessage()); + } + } + + /** + * Processes the given file using the given cipher + * + * @param cipher cipher used for encryption or decryption + * @param file path of the file to be encrypted or decrypted + * @throws IOException if file could not be found + */ + + private static void fileToBytes(Cipher cipher, File file) throws IOException { + + FileInputStream fileInputStream = null; + FileOutputStream fileOutputStream = null; + try { + fileInputStream = new FileInputStream(file); + byte[] readBytes = new byte[(int) file.length()]; + fileInputStream.read(readBytes); + + byte[] writeBytes = cipher.doFinal(readBytes); + fileOutputStream = new FileOutputStream(file); + fileOutputStream.write(writeBytes); + + } catch (BadPaddingException be) { + logger.info("File might not decoded/encoded properly due to bad padding " + be.getMessage()); + } catch (IllegalBlockSizeException ibe) { + logger.info("Input length size must be in multiple of 16 " + ibe.getMessage()); + } finally { + try { + if (fileInputStream != null) { + fileInputStream.close(); + } + if (fileOutputStream != null) { + fileOutputStream.close(); + } + } catch (IOException ioe) { + logger.info("File streams could not be closed " + ioe.getMessage()); + } + } + } + + /** + * Method to generate a SecretKey using the password provided + * + * @return SecretKey generated using AES encryption + */ + public static SecretKey generateKey() { + final String password = "EncryptionisImportant"; + SecretKeySpec secretKeySpec = null; + try { + salt = getSalt(); + MessageDigest digester = MessageDigest.getInstance("SHA-256"); + digester.update(password.getBytes("UTF-8")); + byte[] key = digester.digest(); + secretKeySpec = new SecretKeySpec(key , 0 , 16 , "AES"); + } catch (NoSuchAlgorithmException nae) { + logger.info("Algorithm Unsupported " + nae.getMessage()); + } catch (UnsupportedEncodingException use) { + logger.info("Encoding Unsupported " + use.getMessage()); + } catch (NoSuchProviderException e) { + logger.severe("The provider is not available " + e.getMessage()); + } + return secretKeySpec; + } +} diff --git a/collated/functional/yungyung04.md b/collated/functional/yungyung04.md new file mode 100644 index 000000000000..3a90891405f3 --- /dev/null +++ b/collated/functional/yungyung04.md @@ -0,0 +1,1526 @@ +# yungyung04 +###### \java\seedu\address\logic\commands\AddPersonalTaskCommand.java +``` java +/** + * Adds a personal task into the schedule. + */ +public class AddPersonalTaskCommand extends UndoableCommand { + + public static final String COMMAND_WORD = "addtask"; + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds a personal task into the schedule.\n" + + "Parameters: " + + "Date(dd/mm/yyyy) " + + "Start time(hh:mm) " + + "Duration(XXhXXm) " + + "Description( anything; leading and trailing whitespaces will be trimmed )\n" + + "Example: " + COMMAND_WORD + " " + + "10/12/2018 " + + "12:30 " + + "1h30m " + + "Yoga"; + public static final String MESSAGE_SUCCESS = "Task added: %1$s"; + + private final PersonalTask toAdd; + + /** + * Creates an AddPersonalTaskCommand to add the specified {@code Task}. + */ + public AddPersonalTaskCommand(PersonalTask task) { + requireNonNull(task); + toAdd = task; + } + +``` +###### \java\seedu\address\logic\commands\AddPersonalTaskCommand.java +``` java + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof AddPersonalTaskCommand // instanceof handles nulls + && toAdd.equals(((AddPersonalTaskCommand) other).toAdd)); + } +} +``` +###### \java\seedu\address\logic\commands\AddTuitionTaskCommand.java +``` java + +/** + * Adds a tuition (task) into the schedule. + */ +public class AddTuitionTaskCommand extends UndoableCommand { + + public static final String COMMAND_WORD = "addtuition"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds a tuition (task) into the schedule.\n" + + "Parameters: " + + "tutee_index" + + "Date(dd/mm/yyyy) " + + "Start time(hh:mm) " + + "Duration(XXhXXm) " + + "Description( anything; leading and trailing whitespaces will be trimmed )\n" + + "Example: " + COMMAND_WORD + " " + + "1 " + + "10/12/2018 " + + "12:30 " + + "1h30m " + + "Calculus homework page 24"; + + public static final String MESSAGE_SUCCESS = "New tuition task added."; + + private final Index targetIndex; + private final LocalDateTime taskdateTime; + private final String duration; + private final String description; + + private TuitionTask toAdd; + private String associatedTutee; + + /** + * Creates an AddTuition to add the specified {@code Task} which is associated to {@code Tutee}. + */ + public AddTuitionTaskCommand(Index targetIndex, LocalDateTime taskDateTime, String duration, String description) { + requireNonNull(taskDateTime); + requireNonNull(duration); + requireNonNull(description); + this.targetIndex = targetIndex; + this.taskdateTime = taskDateTime; + this.duration = duration; + this.description = description; + } + +``` +###### \java\seedu\address\logic\commands\AddTuitionTaskCommand.java +``` java + @Override + protected void preprocessUndoableCommand() throws CommandException { + associatedTutee = getAssociatedTutee().getName().fullName; + toAdd = new TuitionTask(associatedTutee, taskdateTime, duration, description); + } + + /** + * Returns the {@code Tutee} object that is pointed by the index as shown in the last displayed conatct list. + */ + private Tutee getAssociatedTutee() throws CommandException { + List lastShownList = model.getFilteredPersonList(); + + if (targetIndex.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + } + requireNonNull(lastShownList.get(targetIndex.getZeroBased())); + Person associatedPerson = lastShownList.get(targetIndex.getZeroBased()); + if (!(associatedPerson instanceof Tutee)) { + throw new CommandException(Messages.MESSAGE_INVALID_TUTEE_INDEX); + } + return (Tutee) lastShownList.get(targetIndex.getZeroBased()); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof AddTuitionTaskCommand // instanceof handles nulls + && targetIndex.equals(((AddTuitionTaskCommand) other).targetIndex)) + && taskdateTime.equals(((AddTuitionTaskCommand) other).taskdateTime) + && duration.equals(((AddTuitionTaskCommand) other).duration) + && description.equals(((AddTuitionTaskCommand) other).description); + + } +} +``` +###### \java\seedu\address\logic\commands\DeleteTaskCommand.java +``` java +/** + * Deletes a task from the schedule. + */ +public class DeleteTaskCommand extends UndoableCommand { + + public static final String COMMAND_WORD = "deletetask"; + + public static final String MESSAGE_USAGE = + COMMAND_WORD + ": Deletes a tuition or personal task from the schedule.\n" + + "Parameters: " + + "index of Task" + + "Example: " + COMMAND_WORD + " 1"; + + public static final String MESSAGE_SUCCESS = "Deleted task : %1$s"; + + private final Index targetIndex; + private Task toDelete; + + public DeleteTaskCommand(Index indexOfTask) { + targetIndex = indexOfTask; + } + + @Override + public CommandResult executeUndoableCommand() { + requireNonNull(toDelete); + try { + model.deleteTask(toDelete); + } catch (TaskNotFoundException tnfe) { + throw new AssertionError("The target person cannot be missing"); + } + return new CommandResult(String.format(MESSAGE_SUCCESS, toDelete.toString())); + } + + @Override + protected void preprocessUndoableCommand() throws CommandException { + toDelete = getAssociatedTask(); + } + + private Task getAssociatedTask() throws CommandException { + List lastShownTaskList = model.getFilteredTaskList(); + + if (targetIndex.getZeroBased() >= lastShownTaskList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_TASK_DISPLAYED_INDEX); + } + + return lastShownTaskList.get(targetIndex.getZeroBased()); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof DeleteTaskCommand // instanceof handles nulls + && targetIndex.equals(((DeleteTaskCommand) other).targetIndex)) + && Objects.equals(this.toDelete, ((DeleteTaskCommand) other).toDelete); + } +} +``` +###### \java\seedu\address\logic\commands\FindPersonCommand.java +``` java +/** + * Finds and lists all persons in contact list based on the specified filter category. + */ +public class FindPersonCommand extends Command { + public static final String COMMAND_WORD = "findpersonby"; + public static final String COMMAND_ALIAS = "f"; + + public static final String MESSAGE_SUCCESS = "Find is successful."; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": lists all person that suit the specified category\n" + + "Parameters: filter_category keyword\n" + + "Choice of filter_categories: " + + CATEGORY_NAME + ", " + + CATEGORY_EDUCATION_LEVEL + ", " + + CATEGORY_GRADE + ", " + + CATEGORY_SCHOOL + ", " + + CATEGORY_SUBJECT + "\n" + + "Example: " + COMMAND_WORD + " " + CATEGORY_GRADE + " A"; + + private final String category; + private final String[] keywords; + private Predicate personPredicate; + private Predicate taskPredicate; + + public FindPersonCommand(String category, String[] keywords) { + this.category = category; + this.keywords = keywords; + } + + @Override + public CommandResult execute() { + switch (category) { + case CATEGORY_NAME: + personPredicate = new NameContainsKeywordsPredicate(Arrays.asList(keywords)); + model.updateFilteredPersonList(personPredicate); + break; + case CATEGORY_EDUCATION_LEVEL: + personPredicate = new EducationLevelContainsKeywordsPredicate(Arrays.asList(keywords)); + model.updateFilteredPersonList(personPredicate); + break; + case CATEGORY_GRADE: + personPredicate = new GradeContainsKeywordsPredicate(Arrays.asList(keywords)); + model.updateFilteredPersonList(personPredicate); + break; + case CATEGORY_SCHOOL: + personPredicate = new SchoolContainsKeywordsPredicate(Arrays.asList(keywords)); + model.updateFilteredPersonList(personPredicate); + break; + case CATEGORY_SUBJECT: + personPredicate = new SubjectContainsKeywordsPredicate(Arrays.asList(keywords)); + model.updateFilteredPersonList(personPredicate); + break; + default: + // invalid category should be detected in parser instead + assert (false); + } + return new CommandResult(MESSAGE_SUCCESS + "\n" + + getMessageForPersonListShownSummary(model.getFilteredPersonList().size())); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof FindPersonCommand // instanceof handles nulls + && category.equals(((FindPersonCommand) other).category) + && hasSameValue(keywords, ((FindPersonCommand) other).keywords)); + } + + /** + * Returns true if both the given arrays of String contain the same elements. + */ + private boolean hasSameValue(String[] firstKeywords, String[] secondKeywords) { + if (firstKeywords.length != secondKeywords.length) { + return false; + } + + for (int i = 0; i < firstKeywords.length; i++) { + if (!firstKeywords[i].equals(secondKeywords[i])) { + return false; + } + } + return true; + } +} +``` +###### \java\seedu\address\logic\commands\FindTaskCommand.java +``` java +/** + * Finds and lists all tasks in the task list based on the specified filter category. + */ +public class FindTaskCommand extends Command { + public static final String COMMAND_WORD = "findtaskby"; + + public static final String MESSAGE_SUCCESS = "Find is successful."; + + public static final String INPUT_TYPE_BETWEEN = "between"; + public static final String INPUT_TYPE_NAMELY = "namely"; + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": lists all tasks that suit the specified category\n" + + "Parameters: CATEGORY FIND_TYPE KEYWORDS\n" + + "1st Example: " + COMMAND_WORD + " " + CATEGORY_MONTH + " " + INPUT_TYPE_BETWEEN + " April October\n" + + "2nd Example: " + COMMAND_WORD + " " + CATEGORY_MONTH + " " + INPUT_TYPE_NAMELY + + " 2 05 Aug December now"; + + private final String category; + private final String[] keywords; + private Predicate taskPredicate; + + public FindTaskCommand(String category, String[] keywords) { + this.category = category; + this.keywords = keywords; + } + + @Override + public CommandResult execute() { + switch (category) { + case CATEGORY_MONTH: + taskPredicate = new MonthContainsKeywordsPredicate(Arrays.asList(keywords)); + model.updateFilteredTaskList(taskPredicate); + break; + default: + // invalid category should be detected in parser instead + assert (false); + } + return new CommandResult(MESSAGE_SUCCESS + "\n" + + getMessageForTaskListShownSummary(model.getFilteredTaskList().size())); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof FindTaskCommand // instanceof handles nulls + && category.equals(((FindTaskCommand) other).category) + && hasSameValue(keywords, ((FindTaskCommand) other).keywords)); + } + + /** + * Returns true if both the given arrays of String contain the same elements. + */ + private boolean hasSameValue(String[] firstKeywords, String[] secondKeywords) { + if (firstKeywords.length != secondKeywords.length) { + return false; + } + + for (int i = 0; i < firstKeywords.length; i++) { + if (!firstKeywords[i].equals(secondKeywords[i])) { + return false; + } + } + return true; + } +} +``` +###### \java\seedu\address\logic\commands\ListTuteeCommand.java +``` java +/** + * Lists all tutees in the application to the user. + */ +public class ListTuteeCommand extends Command { + + public static final String COMMAND_WORD = "listtutee"; + + public static final String MESSAGE_SUCCESS = "Listed all tutees"; + + @Override + public CommandResult execute() { + model.updateFilteredPersonList(PREDICATE_SHOW_ALL_TUTEES); + return new CommandResult(MESSAGE_SUCCESS); + } +} +``` +###### \java\seedu\address\logic\commands\SortPersonCommand.java +``` java +/** + * Sorts all persons from the last shown list lexicographically according to the specified sorting category. + * Since tutee contains specific information such as grade, + * a Person who is not a tutee will be listed last when such information is selected to be the sorting category. + */ +public class SortPersonCommand extends Command { + public static final String COMMAND_WORD = "sortpersonby"; + public static final String COMMAND_ALIAS = "spb"; + + public static final String MESSAGE_SUCCESS = "sorted list of persons successfully"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": sorts all visible persons lexicographically according to the specified sorting category.\n" + + "Persons who are not Tutees will be listed last when a tutee detail is the selected category " + + "(refer to User Guide)\n" + + "Parameter: sort_category\n" + + "Choice of sort_categories: " + + CATEGORY_NAME + ", " + + CATEGORY_EDUCATION_LEVEL + ", " + + CATEGORY_GRADE + ", " + + CATEGORY_SCHOOL + ", " + + CATEGORY_SUBJECT + "\n" + + "Example: " + COMMAND_WORD + " " + CATEGORY_GRADE; + + private final String category; + private final Comparator comparator; + + public SortPersonCommand(String category) { + this.category = category; + comparator = PersonSortUtil.getComparator(category); + } + + @Override + public CommandResult execute() { + model.sortFilteredPersonList(comparator); + return new CommandResult(MESSAGE_SUCCESS); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof SortPersonCommand // instanceof handles nulls + && category.equals(((SortPersonCommand) other).category)); + } +} +``` +###### \java\seedu\address\logic\commands\SortTaskCommand.java +``` java +/** + * Sorts all tasks from the last shown list according to the specified sorting category in an increasing order + */ +public class SortTaskCommand extends Command { + public static final String COMMAND_WORD = "sorttaskby"; + public static final String COMMAND_ALIAS = "stb"; + public static final String MESSAGE_SUCCESS = "sorted list of tasks successfully"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + "Sorts all visible tasks according to the specified sorting category in an increasing order\n" + + "Parameter: sort_category\n" + + "Choice of sort_categories: " + + CATEGORY_MONTH + ", " + + CATEGORY_DATE_TIME + "\n" + + "Example: " + COMMAND_WORD + " " + CATEGORY_MONTH; + + private final String category; + private final Comparator comparator; + + public SortTaskCommand(String category) { + requireNonNull(category); + this.category = category; + comparator = TaskSortUtil.getComparator(category); + } + + @Override + public CommandResult execute() { + requireNonNull(comparator); + model.sortFilteredTaskList(comparator); + return new CommandResult(MESSAGE_SUCCESS); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof SortTaskCommand // instanceof handles nulls + && category.equals(((SortTaskCommand) other).category)); + } +} +``` +###### \java\seedu\address\logic\parser\AddPersonalTaskCommandParser.java +``` java + +/** + * Parses input arguments and creates a new AddPersonalTaskCommand object. + */ +public class AddPersonalTaskCommandParser implements Parser { + + private static final String INPUT_FORMAT_VALIDATION_REGEX = "(\\d{2}/\\d{2}/\\d{4})\\s\\d{2}:\\d{2}\\s" + + "\\d{1,2}h\\d{1,2}m.*"; + private static final int MAXIMUM_AMOUNT_OF_TASK_PARAMETER = 4; + private static final int INDEX_OF_DATE = 0; + private static final int INDEX_OF_TIME = 1; + private static final int INDEX_OF_DURATION = 2; + + /** + * Parses the given {@code String} of arguments in the context of the AddPersonalTaskCommand + * and returns an AddPersonalTaskCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public AddPersonalTaskCommand parse(String task) throws ParseException { + if (!task.trim().matches(INPUT_FORMAT_VALIDATION_REGEX)) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddPersonalTaskCommand.MESSAGE_USAGE)); + } + + String[] arguments = task.trim().split("\\s+", MAXIMUM_AMOUNT_OF_TASK_PARAMETER); + try { + LocalDateTime taskDateTime = + ParserUtil.parseDateTime(arguments[INDEX_OF_DATE] + " " + arguments[INDEX_OF_TIME]); + String duration = ParserUtil.parseDuration(arguments[INDEX_OF_DURATION]); + String description = ParserUtil.parseDescription(arguments, MAXIMUM_AMOUNT_OF_TASK_PARAMETER); + + return new AddPersonalTaskCommand(new PersonalTask(taskDateTime, duration, description)); + } catch (DateTimeParseException dtpe) { + throw new ParseException(MESSAGE_INVALID_DATE_TIME); + } catch (DurationParseException dpe) { + throw new ParseException(MESSAGE_INVALID_DURATION); + } + } +} +``` +###### \java\seedu\address\logic\parser\AddTuitionTaskCommandParser.java +``` java +/** + * Parses input arguments and creates a new AddTuitionTaskCommand object + */ +public class AddTuitionTaskCommandParser implements Parser { + + private static final String INPUT_FORMAT_VALIDATION_REGEX = "\\d+\\s(\\d{2}/\\d{2}/\\d{4})\\s\\d{2}:\\d{2}\\s" + + "\\d{1,2}h\\d{1,2}m.*"; + private static final int MAXIMUM_AMOUNT_OF_TASK_PARAMETER = 5; + private static final int INDEX_OF_PERSON_INDEX = 0; + private static final int INDEX_OF_DATE = 1; + private static final int INDEX_OF_TIME = 2; + private static final int INDEX_OF_DURATION = 3; + + /** + * Parses the given {@code String} of arguments in the context of the AddTuitionTaskCommand + * and returns an AddTuitionTaskCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public AddTuitionTaskCommand parse(String args) throws ParseException { + if (!args.trim().matches(INPUT_FORMAT_VALIDATION_REGEX)) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddTuitionTaskCommand.MESSAGE_USAGE)); + } + + String[] arguments = args.trim().split("\\s+", MAXIMUM_AMOUNT_OF_TASK_PARAMETER); + try { + Index personIndex = ParserUtil.parseIndex(arguments[INDEX_OF_PERSON_INDEX]); + LocalDateTime taskDateTime = + ParserUtil.parseDateTime(arguments[INDEX_OF_DATE] + " " + arguments[INDEX_OF_TIME]); + String duration = ParserUtil.parseDuration(arguments[INDEX_OF_DURATION]); + String description = ParserUtil.parseDescription(arguments, MAXIMUM_AMOUNT_OF_TASK_PARAMETER); + + return new AddTuitionTaskCommand(personIndex, taskDateTime, duration, description); + } catch (DateTimeParseException dtpe) { + throw new ParseException(MESSAGE_INVALID_DATE_TIME); + } catch (DurationParseException dpe) { + throw new ParseException(MESSAGE_INVALID_DURATION); + } catch (IllegalValueException ive) { + throw new ParseException(MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + } + } +} +``` +###### \java\seedu\address\logic\parser\DeleteTaskCommandParser.java +``` java +/** + * 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 an 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 (IllegalValueException ive) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteTaskCommand.MESSAGE_USAGE)); + } + } +} +``` +###### \java\seedu\address\logic\parser\exceptions\InvalidBoundariesException.java +``` java +/** + * Signals that the given keywords cannot serve as valid boundaries + */ +public class InvalidBoundariesException extends Exception { + public InvalidBoundariesException() {}; +} +``` +###### \java\seedu\address\logic\parser\FindPersonCommandParser.java +``` java +/** + * Parses input arguments and creates a new FindPersonCommand object + */ +public class FindPersonCommandParser implements Parser { + + private static final int EXPECTED_AMOUNT_OF_PARAMETERS = 2; + private static final int INDEX_OF_FILTER_CATEGORY = 0; + private static final int INDEX_OF_KEYWORDS = 1; + + private List validCategories = + new ArrayList<>(Arrays.asList(CATEGORY_NAME, CATEGORY_EDUCATION_LEVEL, CATEGORY_GRADE, + CATEGORY_SCHOOL, CATEGORY_SUBJECT)); + + /** + * Parses the given {@code String} of arguments in the context of the FindPersonCommand + * and returns a FindPersonCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public FindPersonCommand parse(String args) throws ParseException { + String[] arguments = args.trim().toLowerCase().split("\\s+", EXPECTED_AMOUNT_OF_PARAMETERS); + + if (arguments.length < EXPECTED_AMOUNT_OF_PARAMETERS) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindPersonCommand.MESSAGE_USAGE)); + } + + String filterCategory = arguments[INDEX_OF_FILTER_CATEGORY]; + String[] keywords = arguments[INDEX_OF_KEYWORDS].split("\\s+"); + + if (!validCategories.contains(filterCategory)) { + throw new ParseException(String.format(MESSAGE_INVALID_FILTER_CATEGORY, FindPersonCommand.MESSAGE_USAGE)); + } + + return new FindPersonCommand(filterCategory, keywords); + } +} +``` +###### \java\seedu\address\logic\parser\FindTaskCommandParser.java +``` java +/** + * Parses input arguments and creates a new FindTaskCommand object + */ +public class FindTaskCommandParser implements Parser { + + private static final int EXPECTED_AMOUNT_OF_PARAMETERS = 3; + private static final int INDEX_OF_FILTER_CATEGORY = 0; + private static final int INDEX_OF_INPUT_TYPE = 1; + private static final int INDEX_OF_KEYWORDS = 2; + private static final int INDEX_OF_FIRST_KEYWORD = 0; + private static final int INDEX_OF_SECOND_KEYWORD = 1; + private static final int INVALID_MONTH = 0; + private static final int MONTH_WITH_MMM_FORMAT_CHARACTER_LENGTH = 3; + private static final int REQUIRED_AMOUNT_OF_BOUNDARIES = 2; + private static final int MONTH_WITH_MM_FORMAT_CHARACTER_LENGTH = 2; + private static final int AMOUNT_OF_MONTHS = 12; + private static final String INPUT_TYPE_NAMELY = "namely"; + private static final String INPUT_TYPE_BETWEEN = "between"; + private static final DateTimeFormatter FORMATTER_MONTH_MM = new DateTimeFormatterBuilder().parseCaseInsensitive() + .appendPattern("MM").toFormatter(Locale.ENGLISH); + private static final DateTimeFormatter FORMATTER_MONTH_MMM = new DateTimeFormatterBuilder().parseCaseInsensitive() + .appendPattern("MMM").toFormatter(Locale.ENGLISH); + private static final DateTimeFormatter FORMATTER_MONTH_MMMM = new DateTimeFormatterBuilder().parseCaseInsensitive() + .appendPattern("MMMM").toFormatter(Locale.ENGLISH); + + private List validCategories = new ArrayList<>(Arrays.asList(CATEGORY_MONTH)); + private List validInputTypes = new ArrayList<>(Arrays.asList(INPUT_TYPE_NAMELY, INPUT_TYPE_BETWEEN)); + + /** + * Parses the given {@code String} of arguments in the context of the FindTaskCommand + * and returns a FindTaskCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public FindTaskCommand parse(String args) throws ParseException { + String[] arguments = args.trim().toLowerCase().split("\\s+", EXPECTED_AMOUNT_OF_PARAMETERS); + if (arguments.length < EXPECTED_AMOUNT_OF_PARAMETERS) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindTaskCommand.MESSAGE_USAGE)); + } + + String filterCategory = arguments[INDEX_OF_FILTER_CATEGORY]; + if (!validCategories.contains(filterCategory)) { + throw new ParseException(String.format(MESSAGE_INVALID_FILTER_CATEGORY, FindTaskCommand.MESSAGE_USAGE)); + } + + String inputType = arguments[INDEX_OF_INPUT_TYPE]; + if (!validInputTypes.contains(inputType)) { + throw new ParseException(String.format(MESSAGE_INVALID_INPUT_TYPES, FindTaskCommand.MESSAGE_USAGE)); + } + + String[] keywords = arguments[INDEX_OF_KEYWORDS].split("\\s+"); + + try { + switch (filterCategory) { + case CATEGORY_MONTH: + keywords = parseMonthKeywords(inputType, keywords); + break; + default: + assert (false); // should never be called + } + } catch (DateTimeParseException dtpe) { + throw new ParseException(MESSAGE_INVALID_KEYWORD_GIVEN); + } catch (InvalidBoundariesException ibe) { + throw new ParseException(MESSAGE_INVALID_MONTH_RANGE_FORMAT); + } + return new FindTaskCommand(filterCategory, keywords); + } + + /** + * Parses month keywords into the required form for the purpose of creating a FindTaskCommand + * @throws ParseException if the given input type is not recognized. + * @throws DateTimeParseException if any of the keywords given is an invalid month + * @throws InvalidBoundariesException if the given keywords are invalid boundary values + */ + private String[] parseMonthKeywords(String inputType, String[] keywords) throws DateTimeParseException, + InvalidBoundariesException { + int[] months; + String[] convertedKeywords = NaturalLanguageIdentifier.getInstance() + .convertNaturalLanguagesIntoMonths(keywords); + months = parseMonthsAsIntegers(convertedKeywords); + if (inputType.equals(INPUT_TYPE_BETWEEN)) { + if (!hasValidMonthBoundaries(months)) { + throw new InvalidBoundariesException(); + } + months = getAllMonthsBetweenBoundaries(months[INDEX_OF_FIRST_KEYWORD], months[INDEX_OF_SECOND_KEYWORD]); + } + convertedKeywords = convertIntoStrings(months); + return convertedKeywords; + } + + /** + * Converts an array of integer into an array of String with the same value. + */ + private String[] convertIntoStrings(int[] integers) { + String[] strings = new String[integers.length]; + for (int i = 0; i < integers.length; i++) { + strings[i] = Integer.toString(integers[i]); + } + return strings; + } + + /** + * Returns all months given two month boundaries. + */ + private int[] getAllMonthsBetweenBoundaries(int lowerBoundary, int upperBoundary) { + int monthDifference; + int[] monthsWithinRange; + + if (lowerBoundary < upperBoundary) { + monthDifference = upperBoundary - lowerBoundary + 1; + monthsWithinRange = new int[monthDifference]; + for (int i = 0; i < monthDifference; i++) { + monthsWithinRange[i] = lowerBoundary + i; + } + } else { + monthDifference = upperBoundary + AMOUNT_OF_MONTHS + 1 - lowerBoundary; + monthsWithinRange = new int[monthDifference]; + for (int i = 0; i < monthDifference; i++) { + if (lowerBoundary + i <= AMOUNT_OF_MONTHS) { + monthsWithinRange[i] = lowerBoundary + i; + } else { + monthsWithinRange[i] = lowerBoundary + i - AMOUNT_OF_MONTHS; + } + } + } + return monthsWithinRange; + } + + /** + * Returns true if the given months are valid boundaries. + */ + private boolean hasValidMonthBoundaries(int[] months) { + return months.length == REQUIRED_AMOUNT_OF_BOUNDARIES + && months[INDEX_OF_FIRST_KEYWORD] != months[INDEX_OF_SECOND_KEYWORD]; + } + + /** + * Parses given {@code String[]} of months into their integer representation. + * @throws DateTimeParseException if any of the given month is invalid. + */ + private int[] parseMonthsAsIntegers(String[] keywords) throws DateTimeParseException { + int[] months = new int[keywords.length]; + for (int i = 0; i < keywords.length; i++) { + months[i] = parseMonthAsInteger(keywords[i]); + } + return months; + } + + /** + * Parses given {@code String} of month into its integer representation. + * @throws DateTimeParseException if the given month is invalid. + */ + private int parseMonthAsInteger(String monthString) throws DateTimeParseException { + TemporalAccessor accessor; + int month = INVALID_MONTH; + if (monthString.length() < MONTH_WITH_MM_FORMAT_CHARACTER_LENGTH) { + checkMonthWithMFormat(monthString); + accessor = FORMATTER_MONTH_MM.parse("0" + monthString); + month = accessor.get(ChronoField.MONTH_OF_YEAR); + } else if (monthString.length() == MONTH_WITH_MM_FORMAT_CHARACTER_LENGTH) { + checkMonthWithMmFormat(monthString); + accessor = FORMATTER_MONTH_MM.parse(monthString); + month = accessor.get(ChronoField.MONTH_OF_YEAR); + } else if (monthString.length() == MONTH_WITH_MMM_FORMAT_CHARACTER_LENGTH) { + accessor = FORMATTER_MONTH_MMM.parse(monthString); + month = accessor.get(ChronoField.MONTH_OF_YEAR); + } else if (monthString.length() > MONTH_WITH_MMM_FORMAT_CHARACTER_LENGTH) { + accessor = FORMATTER_MONTH_MMMM.parse(monthString); + month = accessor.get(ChronoField.MONTH_OF_YEAR); + } + return month; + } + + /** + * Checks whether a given month is a valid month with m format + * @param monthString the month provided by user input + * @throws DateTimeParseException if the given month is invalid. + */ + private void checkMonthWithMFormat(String monthString) throws DateTimeParseException { + try { + int tempMonth = Integer.parseInt(monthString); + if (tempMonth <= INVALID_MONTH) { + throw new DateTimeParseException(MESSAGE_INVALID_DATE_TIME, monthString, 0); + } + } catch (NumberFormatException nfe) { + throw new DateTimeParseException(MESSAGE_INVALID_DATE_TIME, monthString, 0); + } + } + + /** + * Checks whether a given month is a valid month with mm format + * @param monthString the month provided by user input + * @throws DateTimeParseException if the given month is invalid. + */ + private void checkMonthWithMmFormat(String monthString) throws DateTimeParseException { + try { + int tempMonth = Integer.parseInt(monthString); + if (tempMonth > AMOUNT_OF_MONTHS) { + throw new DateTimeParseException(MESSAGE_INVALID_DATE_TIME, monthString, 0); + } + } catch (NumberFormatException nfe) { + throw new DateTimeParseException(MESSAGE_INVALID_DATE_TIME, monthString, 0); + } + } +} +``` +###### \java\seedu\address\logic\parser\NaturalLanguageIdentifier.java +``` java +/** + * Provides utilities to recognize and translate natural language from user input into processable values + */ +public class NaturalLanguageIdentifier { + public static final String NATURAL_NOW = "now"; + public static final String NATURAL_TODAY = "today"; + public static final String NATURAL_CURRENT = "current"; + public static final String NATURAL_LAST = "last"; + public static final String NATURAL_THIS = "this"; + public static final String NATURAL_NEXT = "next"; + public static final String NATURAL_MONTH = "month"; + public static final String NATURAL_LAST_MONTH = NATURAL_LAST + " " + NATURAL_MONTH; + public static final String NATURAL_THIS_MONTH = NATURAL_THIS + " " + NATURAL_MONTH; + public static final String NATURAL_NEXT_MONTH = NATURAL_NEXT + " " + NATURAL_MONTH; + public static final String NATURAL_CURRENT_MONTH = NATURAL_CURRENT + " " + NATURAL_MONTH; + + private static List twoWordedNaturalLanguages = new ArrayList<>(Arrays.asList( + NATURAL_LAST_MONTH, NATURAL_THIS_MONTH, NATURAL_NEXT_MONTH, NATURAL_CURRENT_MONTH)); + + private static NaturalLanguageIdentifier naturalLanguageIdentifier = null; + private LocalDateTime currentDateTime = null; + + /** + * Constructs a NaturalLanguageIdentifier object which stores the current date and time. + */ + private NaturalLanguageIdentifier() { + currentDateTime = LocalDateTime.now(); + } + + /** + * Returns an instance of NaturalLanguageIdentifier object + */ + public static NaturalLanguageIdentifier getInstance() { + if (naturalLanguageIdentifier == null) { + naturalLanguageIdentifier = new NaturalLanguageIdentifier(); + } + return naturalLanguageIdentifier; + } + + /** + * Converts any keywords that are recognizable as month-related natural languages into their month representation. + */ + public String[] convertNaturalLanguagesIntoMonths(String[] keywords) { + requireNonNull(keywords); + String[] mergedKeywords = mergeTwoWordedNaturalLanguage(keywords); + for (int i = 0; i < mergedKeywords.length; i++) { + mergedKeywords[i] = getMonthAsString(mergedKeywords[i]); + } + return mergedKeywords; + } + + /** + * Converts natural language into its month representation if possible. + */ + public String getMonthAsString(String userInput) { + requireNonNull(userInput); + String result; + switch (userInput) { + case NATURAL_TODAY: + //Fallthrough + case NATURAL_NOW: + //Fallthrough + case NATURAL_CURRENT_MONTH: + //Fallthrough + case NATURAL_THIS_MONTH: + result = currentDateTime.getMonth().name(); + break; + case NATURAL_LAST_MONTH: + result = currentDateTime.minusMonths(1).getMonth().name(); + break; + case NATURAL_NEXT_MONTH: + result = currentDateTime.plusMonths(1).getMonth().name(); + break; + default: + result = userInput; + } + return result; + } + + /** + * Merges 2 adjoin Strings if the merged form is a valid natural language. + * Keywords are case-sensitive. + */ + public static String[] mergeTwoWordedNaturalLanguage(String[] keywords) { + requireNonNull(keywords); + if (keywords.length <= 1) { + return keywords; + } + + ArrayList mergedKeywords = new ArrayList<>(); + for (int i = 0; i < keywords.length; i++) { + if (i < (keywords.length - 1) && isMergeable(keywords[i], keywords[i + 1])) { + mergedKeywords.add(keywords[i] + " " + keywords[i + 1]); + i++; + } else { + mergedKeywords.add(keywords[i]); + } + } + return mergedKeywords.toArray(new String[mergedKeywords.size()]); + } + + /** + * Checks whether 2 given words can form a valid natural language. + */ + private static boolean isMergeable(String prefix, String suffix) { + return twoWordedNaturalLanguages.contains(prefix + " " + suffix); + } + +} +``` +###### \java\seedu\address\logic\parser\ParserUtil.java +``` java + /** + * Parses a {@code String dateTime} into an {@code LocalDateTime}. + * + * @throws DateTimeParseException if the given {@code stringDateTime} is invalid. + */ + public static LocalDateTime parseDateTime(String stringDateTime) throws DateTimeParseException { + requireNonNull(stringDateTime); + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd/MM/uuuu HH:mm") + .withResolverStyle(ResolverStyle.STRICT); + return LocalDateTime.parse(stringDateTime, formatter); + } + + /** + * Returns a valid duration + * + * @throws DurationParseException if the given {@code duration} is invalid. + */ + public static String parseDuration(String duration) throws DurationParseException { + requireNonNull(duration); + if (!isValidDuration(duration)) { + throw new DurationParseException(MESSAGE_INVALID_DURATION); + } + return duration; + } + + /** + * Returns true if the given duration is valid. + */ + private static boolean isValidDuration(String duration) { + String durationValidationRegex = "([0-9]|1[0-9]|2[0-3])h([0-5][0-9]|[0-9])m"; + return duration.matches(durationValidationRegex) && !duration.equals(ZERO_DURATION_FIRST_FORMAT) + && !duration.equals(ZERO_DURATION_SECOND_FORMAT); + } + + /** + * Returns a valid task description. + * If description does not exist, returns an empty String. + */ + public static String parseDescription(String[] userInputs, int numberOfParametersWhenDescriptionExist) { + requireNonNull(userInputs); + requireNonNull(numberOfParametersWhenDescriptionExist); + if (isEmptyDescription(userInputs, numberOfParametersWhenDescriptionExist)) { + return EMPTY_STRING; + } else { + String description = getLastElement(userInputs); + return description; + } + } + + /** + * Returns the last element of an array of Strings. + */ + private static String getLastElement(String[] userInputs) { + return userInputs[userInputs.length - 1]; + } + + /** + * Returns true if the given task arguments contain a task description. + */ + private static boolean isEmptyDescription(String[] arguments, int numberOfParametersWhenDescriptionExist) { + return arguments.length < numberOfParametersWhenDescriptionExist; + } +} +``` +###### \java\seedu\address\logic\parser\SortTaskCommandParser.java +``` java +/** + * Parses input arguments and creates a new SortTaskCommand object + */ +public class SortTaskCommandParser implements Parser { + + private static final String SORT_CATEGORY_VALIDATION_REGEX = "\\p{Alpha}+"; + + private List validCategories = + new ArrayList<>(Arrays.asList(CATEGORY_MONTH, CATEGORY_DATE_TIME)); + + /** + * Parses the given {@code String} of arguments in the context of the SortTaskCommand + * and returns a SortPersonCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public SortTaskCommand parse(String args) throws ParseException { + String sortCategory = args.trim().toLowerCase(); + + if (!sortCategory.matches(SORT_CATEGORY_VALIDATION_REGEX)) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, SortPersonCommand.MESSAGE_USAGE)); + } + if (!validCategories.contains(sortCategory)) { + throw new ParseException(String.format(MESSAGE_INVALID_SORTER_CATEGORY, SortPersonCommand.MESSAGE_USAGE)); + } + return new SortTaskCommand(sortCategory); + } +} +``` +###### \java\seedu\address\model\AddressBook.java +``` java + /** + * Removes {@code Tag} from a particular {@code Person}. + * @throws PersonNotFoundException if {@code Person} does not exist. + */ + public void removeTagFromPerson(Tag tag, Person person) { + Set newTags = new HashSet<>(person.getTags()); + + if (!newTags.remove(tag)) { + return; + } + + if (tag.tagName.equals("Tutee")) { + return; + } + + Person newPerson = + new Person(person.getName(), person.getPhone(), person.getEmail(), person.getAddress(), newTags); + + try { + updatePerson(person, newPerson); + } catch (DuplicatePersonException dpe) { + throw new AssertionError("tag modification should not cause duplicate person"); + } catch (PersonNotFoundException pnfe) { + throw new AssertionError("target person does not exist."); + } + } + //author + /** + * Removes {@code key} from this {@code AddressBook}. + * @throws PersonNotFoundException if the {@code key} is not in this {@code AddressBook}. + */ + public boolean removePerson(Person key) throws PersonNotFoundException { + if (persons.remove(key)) { + return true; + } else { + throw new PersonNotFoundException(); + } + } + +``` +###### \java\seedu\address\model\ModelManager.java +``` java + @Override + public void sortFilteredPersonList(Comparator comparator) { + sortedPersons.setComparator(comparator); + } + + @Override + public void sortFilteredTaskList(Comparator comparator) { + sortedTasks.setComparator(comparator); + } + +``` +###### \java\seedu\address\model\person\PersonSortUtil.java +``` java +/** + * Provides utilities for sorting a list of Persons. + */ +public class PersonSortUtil { + public static final String CATEGORY_NAME = "name"; + public static final String CATEGORY_EDUCATION_LEVEL = "edu"; + public static final String CATEGORY_GRADE = "grade"; + public static final String CATEGORY_SCHOOL = "school"; + public static final String CATEGORY_SUBJECT = "subject"; + public static final int NEGATIVE_DIGIT = -1; + public static final int POSITIVE_DIGIT = 1; + + private static final Logger logger = LogsCenter.getLogger(PersonSortUtil.class); + + /** + * Returns the appropriate Person comparator given the sorting category. + */ + public static Comparator getComparator(String sortCategory) { + requireNonNull(sortCategory); + Comparator comparator = null; + + switch (sortCategory) { + case CATEGORY_NAME: + comparator = getNameComparator(); + break; + case CATEGORY_EDUCATION_LEVEL: + comparator = getEducationLevelComparator(); + break; + case CATEGORY_GRADE: + comparator = getGradeComparator(); + break; + case CATEGORY_SCHOOL: + comparator = getSchoolComparator(); + break; + case CATEGORY_SUBJECT: + comparator = getSubjectComparator(); + break; + default: + logger.severe("an invalid category is identified in PersonSortUtil class."); + assert (false); //invalid sortCategory should be identified in parser. + } + return comparator; + } + + /** + * Returns a comparator which is useful to sort education level of a Tutee in an increasing lexicographical order.. + * Non tutees are listed last according to their names in an increasing lexicographical order. + */ + private static Comparator getEducationLevelComparator() { + return new Comparator() { + @Override + public int compare(Person person1, Person person2) { + int result = 0; //value will be replaced + if (areBothTutees(person1, person2)) { + + String personEducationLevel1 = ((Tutee) person1).getEducationLevel().toString(); + String personEducationLevel2 = ((Tutee) person2).getEducationLevel().toString(); + + result = personEducationLevel1.compareToIgnoreCase(personEducationLevel2); + } else if (isFirstTutee(person1, person2)) { + result = NEGATIVE_DIGIT; + } else if (isSecondTutee(person1, person2)) { + result = POSITIVE_DIGIT; + } else if (areNotTutees(person1, person2)) { + result = compareNameLexicographically(person1, person2); + } else { + assert (false); //should never reach this statement -> works as safety measure + } + return result; + } + }; + } + + /** + * Returns a comparator which is useful to sort grade Tutees in an increasing lexicographical order.. + * Non tutees are listed last according to their names in an increasing lexicographical order. + */ + private static Comparator getGradeComparator() { + return new Comparator() { + @Override + public int compare(Person person1, Person person2) { + int result = 0; //value will be replaced + if (areBothTutees(person1, person2)) { + + String personGrade1 = ((Tutee) person1).getGrade().toString(); + String personGrade2 = ((Tutee) person2).getGrade().toString(); + + result = personGrade1.compareToIgnoreCase(personGrade2); + } else if (isFirstTutee(person1, person2)) { + result = NEGATIVE_DIGIT; + } else if (isSecondTutee(person1, person2)) { + result = POSITIVE_DIGIT; + } else if (areNotTutees(person1, person2)) { + result = compareNameLexicographically(person1, person2); + } else { + assert (false); //should never reach this statement + } + return result; + } + }; + } + + /** + * Returns a comparator which is useful to sort school of Tutees in an increasing lexicographical order. + * Non tutees are listed last according to their names in an increasing lexicographical order. + */ + private static Comparator getSchoolComparator() { + return new Comparator() { + @Override + public int compare(Person person1, Person person2) { + int result = 0; //value will be replaced + if (areBothTutees(person1, person2)) { + + String personSchool1 = ((Tutee) person1).getSchool().toString(); + String personSchool2 = ((Tutee) person2).getSchool().toString(); + + result = personSchool1.compareToIgnoreCase(personSchool2); + } else if (isFirstTutee(person1, person2)) { + result = NEGATIVE_DIGIT; + } else if (isSecondTutee(person1, person2)) { + result = POSITIVE_DIGIT; + } else if (areNotTutees(person1, person2)) { + result = compareNameLexicographically(person1, person2); + } else { + assert (false); //should never reach this statement + } + return result; + } + }; + } + + /** + * Returns a comparator which is useful to sort subject of Tutees in an increasing lexicographical order. + * Non tutees are listed last according to their names in an increasing lexicographical order. + */ + private static Comparator getSubjectComparator() { + return new Comparator() { + @Override + public int compare(Person person1, Person person2) { + int result = 0; //value will be replaced + if (areBothTutees(person1, person2)) { + + String personSubject1 = ((Tutee) person1).getSubject().toString(); + String personSubject2 = ((Tutee) person2).getSubject().toString(); + + result = personSubject1.compareToIgnoreCase(personSubject2); + } else if (isFirstTutee(person1, person2)) { + result = NEGATIVE_DIGIT; + } else if (isSecondTutee(person1, person2)) { + result = POSITIVE_DIGIT; + } else if (areNotTutees(person1, person2)) { + result = compareNameLexicographically(person1, person2); + } else { + assert (false); //should never reach this statement + } + return result; + } + }; + } + + /** + * Returns a comparator which is useful to sort name of Persons in an increasing lexicographical order. + */ + private static Comparator getNameComparator() { + return new Comparator() { + @Override + public int compare(Person person1, Person person2) { + return compareNameLexicographically(person1, person2); + } + }; + } + + /** + * Returns true if both the given {@code Person} are subclass of {@code Tutee} + */ + private static boolean areNotTutees(Person person1, Person person2) { + return !(person1 instanceof Tutee || person2 instanceof Tutee); + } + + /** + * Returns true if the given {@code person1} is the only subclass of {@code Tutee} + */ + private static boolean isSecondTutee(Person person1, Person person2) { + return !(person1 instanceof Tutee) && person2 instanceof Tutee; + } + + /** + * Returns true if the given {@code person2} is the only subclass of {@code Tutee} + */ + private static boolean isFirstTutee(Person person1, Person person2) { + return person1 instanceof Tutee && !(person2 instanceof Tutee); + } + + /** + * Returns true if both the given {@code Person} are not subclass of {@code Tutee} + */ + private static boolean areBothTutees(Person person1, Person person2) { + return person1 instanceof Tutee && person2 instanceof Tutee; + } + + /** + * Compares the name of 2 given persons and returns an integer according to their lexicographical relationn + * Integer returned follows the behaviour of {@code compareTo} in Java.lang.String + * + * @param person1 first person to be compared + * @param person2 second person to be compared + */ + public static int compareNameLexicographically(Person person1, Person person2) { + String personName1 = person1.getName().toString(); + String personName2 = person2.getName().toString(); + + return personName1.compareToIgnoreCase(personName2); + } +} +``` +###### \java\seedu\address\model\personal\PersonalTask.java +``` java + @Override + public String toString() { + if (hasDescription()) { + return "Personal task with description " + description + " on " + + Integer.toString(taskDateTime.getDayOfMonth()) + " " + + taskDateTime.getMonth().name() + " " + Integer.toString(taskDateTime.getYear()); + } else { + return "Personal task without description on " + Integer.toString(taskDateTime.getDayOfMonth()) + + " " + taskDateTime.getMonth().name() + " " + Integer.toString(taskDateTime.getYear()); + } + } + + /** + * Returns true if the tuition task contains a non-empty description. + */ + private boolean hasDescription() { + return !description.equals(NULL_STRING); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof PersonalTask // instanceof handles nulls + && taskDateTime.equals(((PersonalTask) other).taskDateTime) + && duration.equals(((PersonalTask) other).duration) + && description.equals(((PersonalTask) other).description)); + } + +} +``` +###### \java\seedu\address\model\task\exceptions\TaskNotFoundException.java +``` java +/** + * Signals that the operation is unable to find the specified task. + */ +public class TaskNotFoundException extends Exception {} +``` +###### \java\seedu\address\model\task\MonthContainsKeywordsPredicate.java +``` java +/** + * Tests that a {@code Task}'s month matches any of the {@code int month} given. + */ +public class MonthContainsKeywordsPredicate implements Predicate { + private final List keywords; + + public MonthContainsKeywordsPredicate(List keywords) { + this.keywords = keywords; + } + + @Override + public boolean test(Task task) { + return keywords.stream() + .anyMatch(keyword -> StringUtil.containsWordIgnoreCase( + Integer.toString(task.getTaskDateTime().getMonthValue()), keyword)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof seedu.address.model.task.MonthContainsKeywordsPredicate // instanceof handles nulls + && this.keywords + .equals(((seedu.address.model.task.MonthContainsKeywordsPredicate) other).keywords)); // state check + } +} +``` +###### \java\seedu\address\model\task\TaskSortUtil.java +``` java +/** + * Provides utilities for sorting a list of Tasks. + */ +public class TaskSortUtil { + public static final String CATEGORY_DATE_TIME = "datetime"; + public static final String CATEGORY_MONTH = "month"; + public static final int NEGATIVE_DIGIT = -1; + public static final int POSITIVE_DIGIT = 1; + + private static final Logger logger = LogsCenter.getLogger(TaskSortUtil.class); + + /** + * Returns the appropriate Task comparator given the sorting category + */ + public static Comparator getComparator(String sortCategory) { + Comparator comparator = null; + + switch (sortCategory) { + case CATEGORY_MONTH: + comparator = getMonthComparator(); + break; + case CATEGORY_DATE_TIME: + comparator = getDateTimeComparator(); + break; + default: + logger.severe("an invalid category is identified in TaskSortUtil class."); + assert (false); //invalid sortCategory should be identified in parser. + } + return comparator; + } + + /** + * Returns a comparator which is useful for sorting tasks based on the month sequence in an increasing order. + */ + private static Comparator getMonthComparator() { + return new Comparator() { + @Override + public int compare(Task task1, Task task2) { + int month1 = task1.getTaskDateTime().getMonthValue(); + int month2 = task2.getTaskDateTime().getMonthValue(); + + if (month1 != month2) { + return compareByMonth(month1, month2); + } else { + return compareByTime(task1.getTaskDateTime(), task2.getTaskDateTime()); + } + } + }; + } + + /** + * Returns a comparator which is useful for sorting tasks based on the date and time sequence in increasing order. + */ + private static Comparator getDateTimeComparator() { + return new Comparator() { + @Override + public int compare(Task task1, Task task2) { + return compareByTime(task1.getTaskDateTime(), task2.getTaskDateTime()); + } + }; + } + + /** + * Compares the 2 given months and returns an integer according to their sequence in standard Gregorian calendar. + */ + private static int compareByMonth(int month1, int month2) { + if (month1 < month2) { + return NEGATIVE_DIGIT; + } else { + return POSITIVE_DIGIT; + } + } + /** + * Compares the 2 given {@code LocalDateTime} and + * Returns an integer according to their sequence in standard Gregorian calendar. + */ + private static int compareByTime(LocalDateTime dateTime1, LocalDateTime dateTime2) { + assert (!dateTime1.isEqual(dateTime2)); //time should be different due to thrown exception when task is added + if (dateTime1.isBefore(dateTime2)) { + return NEGATIVE_DIGIT; + } else { + return POSITIVE_DIGIT; + } + } +} +``` +###### \java\seedu\address\model\tutee\TuitionTask.java +``` java + @Override + public String toString() { + if (hasDescription()) { + return "Tuition task with description " + description + " on " + + Integer.toString(taskDateTime.getDayOfMonth()) + " " + taskDateTime.getMonth().name() + + " " + Integer.toString(taskDateTime.getYear()); + } else { + return "Tuition task without description on " + Integer.toString(taskDateTime.getDayOfMonth()) + + " " + taskDateTime.getMonth().name() + " " + Integer.toString(taskDateTime.getYear()); + } + } + + /** + * Returns true if the tuition task contains a non-empty description. + */ + private boolean hasDescription() { + return !description.equals(NULL_STRING); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof TuitionTask // instanceof handles nulls + && tutee.equals(((TuitionTask) other).tutee) + && taskDateTime.equals(((TuitionTask) other).taskDateTime) + && duration.equals(((TuitionTask) other).duration) + && description.equals(((TuitionTask) other).description)); + } +} +``` +###### \java\seedu\address\storage\XmlAdaptedPerson.java +``` java + if (isTutee(personTags)) { + if (this.subject == null) { + throw new IllegalValueException( + String.format(MISSING_FIELD_MESSAGE_FORMAT, Subject.class.getSimpleName())); + } + if (!Subject.isValidSubject(this.subject)) { + throw new IllegalValueException(Subject.MESSAGE_SUBJECT_CONSTRAINTS); + } + final Subject subject = new Subject(this.subject); + + if (this.grade == null) { + throw new IllegalValueException( + String.format(MISSING_FIELD_MESSAGE_FORMAT, Grade.class.getSimpleName())); + } + if (!Grade.isValidGrade(this.grade)) { + throw new IllegalValueException(Grade.MESSAGE_GRADE_CONSTRAINTS); + } + final Grade grade = new Grade(this.grade); + + if (this.educationLevel == null) { + throw new IllegalValueException( + String.format(MISSING_FIELD_MESSAGE_FORMAT, EducationLevel.class.getSimpleName())); + } + if (!EducationLevel.isValidEducationLevel(this.educationLevel)) { + throw new IllegalValueException(EducationLevel.MESSAGE_EDUCATION_LEVEL_CONSTRAINTS); + } + final EducationLevel educationLevel = new EducationLevel(this.educationLevel); + + if (this.school == null) { + throw new IllegalValueException( + String.format(MISSING_FIELD_MESSAGE_FORMAT, School.class.getSimpleName())); + } + if (!School.isValidSchool(this.school)) { + throw new IllegalValueException(School.MESSAGE_SCHOOL_CONSTRAINTS); + } + final School school = new School(this.school); + + return new Tutee(name, phone, email, address, subject, grade, educationLevel, school, tags); + } else { + return new Person(name, phone, email, address, tags); + } + } +``` diff --git a/collated/test/ChoChihTun.md b/collated/test/ChoChihTun.md new file mode 100644 index 000000000000..b7a44c683369 --- /dev/null +++ b/collated/test/ChoChihTun.md @@ -0,0 +1,1888 @@ +# ChoChihTun +###### \java\guitests\guihandles\CalendarPanelHandle.java +``` java +/** + * A handler for the {@code CalendarPanel} of the UI + */ +public class CalendarPanelHandle extends NodeHandle { + + public static final String CALENDAR_PANEL_ID = "#calendarPlaceholder"; + + private CalendarPanel calendarPanel; + + public CalendarPanelHandle(Node calendarPanelNode) { + super(calendarPanelNode); + calendarPanel = new CalendarPanel(); + } + + public PageBase getDefaultCalendarViewPage() { + return calendarPanel.getRoot().getDayPage(); + } + + public PageBase getWeekViewPage() { + return calendarPanel.getRoot().getWeekPage(); + } + + public PageBase getMonthViewPage() { + return calendarPanel.getRoot().getMonthPage(); + } + + public PageBase getYearViewPage() { + return calendarPanel.getRoot().getYearPage(); + } + + public PageBase getCurrentCalendarViewPage() { + return calendarPanel.getRoot().getSelectedPage(); + } +} +``` +###### \java\seedu\address\logic\commands\AddTuteeCommandTest.java +``` java +/** + * Contains integration tests (interaction with the Model) for {@code AddTuteeCommand}. + */ +public class AddTuteeCommandTest { + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @Test + public void constructor_nullTutee_throwsNullPointerException() { + thrown.expect(NullPointerException.class); + new AddTuteeCommand(null); + } + + @Test + public void execute_tuteeAcceptedByModel_addSuccessful() throws Exception { + ModelStubAcceptingPersonAdded modelStub = new ModelStubAcceptingPersonAdded(); + Tutee validTutee = new TuteeBuilder().build(); + + CommandResult commandResult = getAddTuteeCommandForTutee(validTutee, modelStub).execute(); + + assertEquals(String.format(AddTuteeCommand.MESSAGE_SUCCESS, validTutee), commandResult.feedbackToUser); + assertEquals(Arrays.asList(validTutee), modelStub.personsAdded); + } + + @Test + public void execute_duplicatePerson_throwsCommandException() throws Exception { + ModelStub modelStub = new ModelStubThrowingDuplicatePersonException(); + Tutee validTutee = new TuteeBuilder().build(); + + thrown.expect(CommandException.class); + thrown.expectMessage(AddTuteeCommand.MESSAGE_DUPLICATE_PERSON); + + getAddTuteeCommandForTutee(validTutee, modelStub).execute(); + } + + @Test + public void equals_validArgs_returnsTrue() { + Tutee alice = new TuteeBuilder().withName("Alice").build(); + AddTuteeCommand addAliceCommand = new AddTuteeCommand(alice); + + // same object + assertTrue(addAliceCommand.equals(addAliceCommand)); + + // objects with same value + AddTuteeCommand addAliceCommandCopy = new AddTuteeCommand(alice); + assertTrue(addAliceCommand.equals(addAliceCommandCopy)); + } + + @Test + public void equals_invalidArgs_returnsFalse() { + Tutee alice = new TuteeBuilder().withName("Alice").build(); + Tutee bob = new TuteeBuilder().withName("Bob").build(); + AddTuteeCommand addAliceCommand = new AddTuteeCommand(alice); + AddTuteeCommand addBobCommand = new AddTuteeCommand(bob); + + // null + assertFalse(addAliceCommand.equals(null)); + + // wrong parameter data type + assertFalse(addAliceCommand.equals(1)); + + // different tutee + assertFalse(addAliceCommand.equals(addBobCommand)); + } + + /** + * Generates a new AddTuteeCommand with the details of the given tutee. + */ + private AddTuteeCommand getAddTuteeCommandForTutee(Tutee tutee, Model model) { + AddTuteeCommand command = new AddTuteeCommand(tutee); + command.setData(model, new CommandHistory(), new UndoRedoStack()); + return command; + } + + /** + * A Model stub that always throw a DuplicatePersonException when trying to add a person. + */ + private class ModelStubThrowingDuplicatePersonException extends ModelStub { + @Override + // A tutee is a person + public void addPerson(Person person) throws DuplicatePersonException { + throw new DuplicatePersonException(); + } + + @Override + public ReadOnlyAddressBook getAddressBook() { + return new AddressBook(); + } + } + + /** + * A Model stub that always accept the person being added. + */ + private class ModelStubAcceptingPersonAdded extends ModelStub { + final ArrayList personsAdded = new ArrayList<>(); + + @Override + // A tutee is a person + public void addPerson(Person person) throws DuplicatePersonException { + requireNonNull(person); + personsAdded.add(person); + } + + @Override + public ReadOnlyAddressBook getAddressBook() { + return new AddressBook(); + } + } + +} +``` +###### \java\seedu\address\logic\commands\EditCommandTest.java +``` java + @Test + public void execute_editTuteeFields_success() { + // Address book with typical tutee inside + model = new ModelManager(getTypicalAddressBook2(), new UserPrefs()); + + Tutee tuteeToEdit = new TuteeBuilder().build(); + EditPersonDescriptor descriptor = new EditPersonDescriptorBuilder(tuteeToEdit).build(); + EditCommand editCommand = prepareCommand(INDEX_FIRST_PERSON, descriptor); + + String expectedMessage = String.format(EditCommand.MESSAGE_EDIT_PERSON_SUCCESS, tuteeToEdit); + + Model expectedModel = new ModelManager(new AddressBook(model.getAddressBook()), new UserPrefs()); + + assertCommandSuccess(editCommand, model, expectedMessage, expectedModel); + } + + @Test + public void execute_invalidTagForPerson_failure() { + EditPersonDescriptor descriptor = new EditPersonDescriptorBuilder().withTags(TUTEE_TAG).build(); + EditCommand editCommand = prepareCommand(INDEX_FIRST_PERSON, descriptor); + assertCommandFailure(editCommand, model, MESSAGE_INVALID_PERSON_TO_EDIT); + } + + @Test + public void execute_invalidFieldsEditedForPerson_failure() { + // Edit subject + EditPersonDescriptor descriptor = new EditPersonDescriptorBuilder().withSubject(VALID_SUBJECT_AMY).build(); + EditCommand editCommand = prepareCommand(INDEX_FIRST_PERSON, descriptor); + assertCommandFailure(editCommand, model, MESSAGE_INVALID_PERSON_TO_EDIT); + + // Edit grade + descriptor = new EditPersonDescriptorBuilder().withGrade(VALID_GRADE_AMY).build(); + editCommand = prepareCommand(INDEX_FIRST_PERSON, descriptor); + assertCommandFailure(editCommand, model, MESSAGE_INVALID_PERSON_TO_EDIT); + + // Edit education level + descriptor = new EditPersonDescriptorBuilder().withEducationLevel(VALID_EDUCATION_LEVEL_AMY).build(); + editCommand = prepareCommand(INDEX_FIRST_PERSON, descriptor); + assertCommandFailure(editCommand, model, MESSAGE_INVALID_PERSON_TO_EDIT); + + // Edit school + descriptor = new EditPersonDescriptorBuilder().withSchool(VALID_SCHOOL_AMY).build(); + editCommand = prepareCommand(INDEX_FIRST_PERSON, descriptor); + assertCommandFailure(editCommand, model, MESSAGE_INVALID_PERSON_TO_EDIT); + } +``` +###### \java\seedu\address\logic\commands\FindTaskCommandTest.java +``` java +/** + * Contains integration tests (interaction with the Model) for {@code FindTaskCommand}. + */ +public class FindTaskCommandTest { + private static final String OCTOBER = "10"; + private static final String NOVEMBER = "11"; + private static final String DECEMBER = "12"; + + private final String[] monthBetweenKeywords = {OCTOBER, NOVEMBER, DECEMBER}; + private final String[] monthNamelyKeyword = {OCTOBER}; + + private final FindTaskCommand findMonthBetweenKeywordsCommand = + new FindTaskCommand(CATEGORY_MONTH, monthBetweenKeywords); + private final FindTaskCommand findMonthNamelyKeywordCommand = + new FindTaskCommand(CATEGORY_MONTH, monthNamelyKeyword); + + private Model model = new ModelManager(getTypicalAddressBook2(), new UserPrefs()); + + @Test + public void equals_validArgs_returnsTrue() { + // same object + assertTrue(findMonthBetweenKeywordsCommand.equals(findMonthBetweenKeywordsCommand)); + + // objects with same value + FindTaskCommand findMonthBetweenKeywordsCommandCopy = new FindTaskCommand(CATEGORY_MONTH, monthBetweenKeywords); + assertTrue(findMonthBetweenKeywordsCommand.equals(findMonthBetweenKeywordsCommandCopy)); + } + + @Test + public void equals_invalidArgs_returnsFalse() { + // null + assertFalse(findMonthBetweenKeywordsCommand.equals(null)); + + // wrong parameter data type + assertFalse(findMonthBetweenKeywordsCommand.equals(1)); + + // different commands + assertFalse(findMonthBetweenKeywordsCommand.equals(findMonthNamelyKeywordCommand)); + } + + @Test + public void execute_findMonth_success() { + // between months + findMonthBetweenKeywordsCommand.setData(model, new CommandHistory(), new UndoRedoStack()); + String expectedMessage = String.format(FindTaskCommand.MESSAGE_SUCCESS + "\n" + + MESSAGE_TASKS_LISTED_OVERVIEW, 3); + assertCommandSuccess(findMonthBetweenKeywordsCommand, expectedMessage, + Arrays.asList(TASK_ALICE, TASK_BENSON, TASK_CARL)); + + // namely month + findMonthNamelyKeywordCommand.setData(model, new CommandHistory(), new UndoRedoStack()); + expectedMessage = String.format(FindTaskCommand.MESSAGE_SUCCESS + "\n" + MESSAGE_TASKS_LISTED_OVERVIEW, 2); + assertCommandSuccess(findMonthNamelyKeywordCommand, expectedMessage, Arrays.asList(TASK_ALICE, TASK_BENSON)); + } + + /** + * Asserts that {@code command} is successfully executed, and
+ * - the command feedback is equal to {@code expectedMessage}
+ * - the {@code FilteredList} is equal to {@code expectedList}
+ * - the {@code AddressBook} in model remains the same after executing the {@code command} + */ + private void assertCommandSuccess(FindTaskCommand command, String expectedMessage, List expectedList) { + AddressBook expectedAddressBook = new AddressBook(model.getAddressBook()); + CommandResult commandResult = command.execute(); + + assertEquals(expectedMessage, commandResult.feedbackToUser); + assertEquals(expectedList, model.getFilteredTaskList()); + assertEquals(expectedAddressBook, model.getAddressBook()); + } +} +``` +###### \java\seedu\address\logic\parser\AddPersonalTaskCommandParserTest.java +``` java +public class AddPersonalTaskCommandParserTest { + private AddPersonalTaskCommandParser parser = new AddPersonalTaskCommandParser(); + + @Test + public void parse_invalidArgs_throwsParseException() { + // Invalid format + assertParseFailure(parser, "a", + String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddPersonalTaskCommand.MESSAGE_USAGE)); + assertParseFailure(parser, "1 11/01/2018 11:11 1h30m Outing", + String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddPersonalTaskCommand.MESSAGE_USAGE)); + assertParseFailure(parser, "11:11 1h30m Outing with friends", + String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddPersonalTaskCommand.MESSAGE_USAGE)); + assertParseFailure(parser, "11/01/2018 1h30m Outing with friends", + String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddPersonalTaskCommand.MESSAGE_USAGE)); + assertParseFailure(parser, "11/01/2018 11:11 Outing with friends", + String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddPersonalTaskCommand.MESSAGE_USAGE)); + assertParseFailure(parser, "11/01/2018 11:11 1h Outing with friends", + String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddPersonalTaskCommand.MESSAGE_USAGE)); + assertParseFailure(parser, "11/01/2018 11:11 30m Outing with friends", + String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddPersonalTaskCommand.MESSAGE_USAGE)); + assertParseFailure(parser, "11:11 32/01/2018 1h30m Outing with friends", + String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddPersonalTaskCommand.MESSAGE_USAGE)); + assertParseFailure(parser, "aa/01/2018 11:11 1h30m Outing with friends", + String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddPersonalTaskCommand.MESSAGE_USAGE)); + assertParseFailure(parser, "32/01/2018 11:aa 1h30m Outing with friends", + String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddPersonalTaskCommand.MESSAGE_USAGE)); + assertParseFailure(parser, "32/01/2018 11:11 1haam Outing with friends", + String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddPersonalTaskCommand.MESSAGE_USAGE)); + + // Invalid date + assertParseFailure(parser, "29/02/2018 11:11 1h30m Outing with friends", MESSAGE_INVALID_DATE_TIME); + assertParseFailure(parser, "31/04/2018 11:11 1h30m Outing with friends", MESSAGE_INVALID_DATE_TIME); + assertParseFailure(parser, "32/01/2018 11:11 1h30m Outing with friends", MESSAGE_INVALID_DATE_TIME); + + // Invalid time + assertParseFailure(parser, "11/01/2018 24:00 1h30m Outing with friends", MESSAGE_INVALID_DATE_TIME); + assertParseFailure(parser, "11/01/2018 11:60 1h30m Outing with friends", MESSAGE_INVALID_DATE_TIME); + + // Invalid duration + assertParseFailure(parser, "11/01/2018 11:11 1h60m Outing with friends", MESSAGE_INVALID_DURATION); + assertParseFailure(parser, "11/01/2018 11:11 24h0m Outing with friends", MESSAGE_INVALID_DURATION); + } + + @Test + public void parse_validArgs_success() { + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd/MM/uuuu HH:mm") + .withResolverStyle(ResolverStyle.STRICT); + + // With description + LocalDateTime taskDateTime = LocalDateTime.parse(VALID_DATE_TIME_AMY, formatter); + PersonalTask personalTask = new PersonalTask(taskDateTime, VALID_DURATION_AMY, VALID_TASK_DESC_AMY); + assertParseSuccess(parser, VALID_TASK_WITH_DESC_AMY, + new AddPersonalTaskCommand(personalTask)); + + // Without description + personalTask = new PersonalTask(taskDateTime, VALID_DURATION_AMY, VALID_EMPTY_TASK_DESC); + assertParseSuccess(parser, VALID_TASK_WITHOUT_DESC_AMY, + new AddPersonalTaskCommand(personalTask)); + + // Valid date + personalTask = new PersonalTask(LocalDateTime.parse("29/02/2016 11:20", formatter), + VALID_DURATION_AMY, VALID_EMPTY_TASK_DESC); + assertParseSuccess(parser, "29/02/2016 11:20 1h11m", + new AddPersonalTaskCommand(personalTask)); + + personalTask = new PersonalTask(LocalDateTime.parse("30/04/2016 11:20", formatter), + VALID_DURATION_AMY, VALID_EMPTY_TASK_DESC); + assertParseSuccess(parser, "30/04/2016 11:20 " + VALID_DURATION_AMY, + new AddPersonalTaskCommand(personalTask)); + + personalTask = new PersonalTask(LocalDateTime.parse("31/01/2016 11:20", formatter), + VALID_DURATION_AMY, VALID_EMPTY_TASK_DESC); + assertParseSuccess(parser, "31/01/2016 11:20 " + VALID_DURATION_AMY, + new AddPersonalTaskCommand(personalTask)); + + // Valid Time + personalTask = new PersonalTask(LocalDateTime.parse("11/01/2018 00:00", formatter), + VALID_DURATION_AMY, VALID_EMPTY_TASK_DESC); + assertParseSuccess(parser, "11/01/2018 00:00 " + VALID_DURATION_AMY, + new AddPersonalTaskCommand(personalTask)); + } +} +``` +###### \java\seedu\address\logic\parser\AddTuitionTaskCommandParserTest.java +``` java +public class AddTuitionTaskCommandParserTest { + private AddTuitionTaskCommandParser parser = new AddTuitionTaskCommandParser(); + + @Test + public void parse_invalidArgs_throwsParseException() { + // Invalid format + assertParseFailure(parser, "1", + String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddTuitionTaskCommand.MESSAGE_USAGE)); + assertParseFailure(parser, "11/01/2018 11:11 1h30m tuition homework", + String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddTuitionTaskCommand.MESSAGE_USAGE)); + assertParseFailure(parser, "1 11:11 1h30m tuition homework", + String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddTuitionTaskCommand.MESSAGE_USAGE)); + assertParseFailure(parser, "1 11/01/2018 1h30m tuition homework", + String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddTuitionTaskCommand.MESSAGE_USAGE)); + assertParseFailure(parser, "1 11/01/2018 11:11 tuition homework", + String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddTuitionTaskCommand.MESSAGE_USAGE)); + assertParseFailure(parser, "1 11/01/2018 11:11 1h tuition homework", + String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddTuitionTaskCommand.MESSAGE_USAGE)); + assertParseFailure(parser, "1 11/01/2018 11:11 30m tuition homework", + String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddTuitionTaskCommand.MESSAGE_USAGE)); + assertParseFailure(parser, "1 11:11 32/01/2018 1h30m tuition homework", + String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddTuitionTaskCommand.MESSAGE_USAGE)); + assertParseFailure(parser, "aaa 32/01/2018 11:11 1h30m tuition homework", + String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddTuitionTaskCommand.MESSAGE_USAGE)); + assertParseFailure(parser, "1 aa/01/2018 11:11 1h30m tuition homework", + String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddTuitionTaskCommand.MESSAGE_USAGE)); + assertParseFailure(parser, "1 32/01/2018 11:aa 1h30m tuition homework", + String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddTuitionTaskCommand.MESSAGE_USAGE)); + assertParseFailure(parser, "1 32/01/2018 11:11 1haam tuition homework", + String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddTuitionTaskCommand.MESSAGE_USAGE)); + + // Invalid date + assertParseFailure(parser, "1 29/02/2018 11:11 1h30m tuition homework", MESSAGE_INVALID_DATE_TIME); + assertParseFailure(parser, "1 31/04/2018 11:11 1h30m tuition homework", MESSAGE_INVALID_DATE_TIME); + assertParseFailure(parser, "1 32/01/2018 11:11 1h30m tuition homework", MESSAGE_INVALID_DATE_TIME); + + // Invalid time + assertParseFailure(parser, "1 11/01/2018 24:00 1h30m tuition homework", MESSAGE_INVALID_DATE_TIME); + assertParseFailure(parser, "1 11/01/2018 11:60 1h30m tuition homework", MESSAGE_INVALID_DATE_TIME); + + // Invalid duration + assertParseFailure(parser, "1 11/01/2018 11:11 1h60m tuition homework", MESSAGE_INVALID_DURATION); + assertParseFailure(parser, "1 11/01/2018 11:11 24h0m tuition homework", MESSAGE_INVALID_DURATION); + } + + @Test + public void parse_validArgs_success() { + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd/MM/uuuu HH:mm") + .withResolverStyle(ResolverStyle.STRICT); + + LocalDateTime taskDateTime = LocalDateTime.parse(VALID_DATE_TIME_AMY, formatter); + + // With description + assertParseSuccess(parser, "1 " + VALID_TASK_WITH_DESC_AMY, + new AddTuitionTaskCommand(INDEX_FIRST_PERSON, taskDateTime, VALID_DURATION_AMY, VALID_TASK_DESC_AMY)); + + // Without description + assertParseSuccess(parser, "1 " + VALID_TASK_WITHOUT_DESC_AMY, + new AddTuitionTaskCommand(INDEX_FIRST_PERSON, taskDateTime, VALID_DURATION_AMY, VALID_EMPTY_TASK_DESC)); + + // Valid date + taskDateTime = LocalDateTime.parse("28/02/2018 11:20", formatter); + assertParseSuccess(parser, "1 28/02/2018 11:20 " + VALID_DURATION_AMY, + new AddTuitionTaskCommand(INDEX_FIRST_PERSON, taskDateTime, VALID_DURATION_AMY, VALID_EMPTY_TASK_DESC)); + + taskDateTime = LocalDateTime.parse("29/02/2016 11:20", formatter); + assertParseSuccess(parser, "1 29/02/2016 11:20 " + VALID_DURATION_AMY, + new AddTuitionTaskCommand(INDEX_FIRST_PERSON, taskDateTime, VALID_DURATION_AMY, VALID_EMPTY_TASK_DESC)); + + taskDateTime = LocalDateTime.parse("30/04/2016 11:20", formatter); + assertParseSuccess(parser, "1 30/04/2016 11:20 " + VALID_DURATION_AMY, + new AddTuitionTaskCommand(INDEX_FIRST_PERSON, taskDateTime, VALID_DURATION_AMY, VALID_EMPTY_TASK_DESC)); + + taskDateTime = LocalDateTime.parse("31/01/2016 11:20", formatter); + assertParseSuccess(parser, "1 31/01/2016 11:20 " + VALID_DURATION_AMY, + new AddTuitionTaskCommand(INDEX_FIRST_PERSON, taskDateTime, VALID_DURATION_AMY, VALID_EMPTY_TASK_DESC)); + + // Valid Time + taskDateTime = LocalDateTime.parse("11/01/2018 00:00", formatter); + assertParseSuccess(parser, "1 11/01/2018 00:00 " + VALID_DURATION_AMY, + new AddTuitionTaskCommand(INDEX_FIRST_PERSON, taskDateTime, VALID_DURATION_AMY, VALID_EMPTY_TASK_DESC)); + } + +} +``` +###### \java\seedu\address\logic\parser\AddTuteeCommandParserTest.java +``` java +/** + * Contains tests for {@code AddTuteeCommandParser}. + */ +public class AddTuteeCommandParserTest { + private AddTuteeCommandParser parser = new AddTuteeCommandParser(); + + @Test + public void parse_allFieldsPresent_success() { + Tutee expectedTutee = new TuteeBuilder().withName(VALID_NAME_BOB).withPhone(VALID_PHONE_BOB) + .withEmail(VALID_EMAIL_BOB).withAddress(VALID_ADDRESS_BOB).withSubject(VALID_SUBJECT_BOB) + .withGrade(VALID_GRADE_BOB).withEducationLevel(VALID_EDUCATION_LEVEL_BOB).withSchool(VALID_SCHOOL_BOB) + .withTags(VALID_TAG_FRIEND).build(); + + // whitespace only preamble + assertParseSuccess(parser, PREAMBLE_WHITESPACE + NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + + ADDRESS_DESC_BOB + SUBJECT_DESC_BOB + GRADE_DESC_BOB + EDUCATION_LEVEL_DESC_BOB + + SCHOOL_DESC_BOB + TAG_DESC_FRIEND, new AddTuteeCommand(expectedTutee)); + + // multiple names - last name accepted + assertParseSuccess(parser, NAME_DESC_AMY + NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + + ADDRESS_DESC_BOB + SUBJECT_DESC_BOB + GRADE_DESC_BOB + EDUCATION_LEVEL_DESC_BOB + + SCHOOL_DESC_BOB + TAG_DESC_FRIEND + TAG_DESC_FRIEND, new AddTuteeCommand(expectedTutee)); + + // multiple phones - last phone accepted + assertParseSuccess(parser, NAME_DESC_BOB + PHONE_DESC_AMY + PHONE_DESC_BOB + EMAIL_DESC_BOB + + ADDRESS_DESC_BOB + SUBJECT_DESC_BOB + GRADE_DESC_BOB + EDUCATION_LEVEL_DESC_BOB + + SCHOOL_DESC_BOB + TAG_DESC_FRIEND, new AddTuteeCommand(expectedTutee)); + + // multiple emails - last email accepted + assertParseSuccess(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_AMY + EMAIL_DESC_BOB + + ADDRESS_DESC_BOB + SUBJECT_DESC_BOB + GRADE_DESC_BOB + EDUCATION_LEVEL_DESC_BOB + + SCHOOL_DESC_BOB + TAG_DESC_FRIEND, new AddTuteeCommand(expectedTutee)); + + // multiple addresses - last address accepted + assertParseSuccess(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + ADDRESS_DESC_AMY + + ADDRESS_DESC_BOB + SUBJECT_DESC_BOB + GRADE_DESC_BOB + EDUCATION_LEVEL_DESC_BOB + + SCHOOL_DESC_BOB + TAG_DESC_FRIEND, new AddTuteeCommand(expectedTutee)); + + // multiple subjects - last subject accepted + assertParseSuccess(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + ADDRESS_DESC_BOB + + SUBJECT_DESC_AMY + SUBJECT_DESC_BOB + GRADE_DESC_BOB + EDUCATION_LEVEL_DESC_BOB + + SCHOOL_DESC_BOB + TAG_DESC_FRIEND, new AddTuteeCommand(expectedTutee)); + + // multiple grades - last grade accepted + assertParseSuccess(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + ADDRESS_DESC_BOB + + SUBJECT_DESC_BOB + GRADE_DESC_AMY + GRADE_DESC_BOB + EDUCATION_LEVEL_DESC_BOB + + SCHOOL_DESC_BOB + TAG_DESC_FRIEND, new AddTuteeCommand(expectedTutee)); + + // multiple education levels - last education level accepted + assertParseSuccess(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + ADDRESS_DESC_BOB + + SUBJECT_DESC_BOB + GRADE_DESC_BOB + EDUCATION_LEVEL_DESC_AMY + EDUCATION_LEVEL_DESC_BOB + + SCHOOL_DESC_BOB + TAG_DESC_FRIEND, new AddTuteeCommand(expectedTutee)); + + // multiple schools - last school accepted + assertParseSuccess(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + ADDRESS_DESC_BOB + + SUBJECT_DESC_BOB + GRADE_DESC_BOB + EDUCATION_LEVEL_DESC_BOB + SCHOOL_DESC_AMY + + SCHOOL_DESC_BOB + TAG_DESC_FRIEND, new AddTuteeCommand(expectedTutee)); + + // multiple tags - all accepted + Tutee expectedTuteeMultipleTags = new TuteeBuilder().withName(VALID_NAME_BOB).withPhone(VALID_PHONE_BOB) + .withEmail(VALID_EMAIL_BOB).withAddress(VALID_ADDRESS_BOB).withSubject(VALID_SUBJECT_BOB) + .withGrade(VALID_GRADE_BOB).withEducationLevel(VALID_EDUCATION_LEVEL_BOB).withSchool(VALID_SCHOOL_BOB) + .withTags(VALID_TAG_FRIEND, VALID_TAG_HUSBAND).build(); + assertParseSuccess(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + ADDRESS_DESC_BOB + + SUBJECT_DESC_BOB + GRADE_DESC_BOB + EDUCATION_LEVEL_DESC_BOB + SCHOOL_DESC_BOB + TAG_DESC_FRIEND + + TAG_DESC_HUSBAND + TAG_DESC_FRIEND, new AddTuteeCommand(expectedTuteeMultipleTags)); + } + + @Test + public void parse_optionalFieldsMissing_success() { + // zero tags + Tutee expectedTutee = new TuteeBuilder().withName(VALID_NAME_AMY).withPhone(VALID_PHONE_AMY) + .withEmail(VALID_EMAIL_AMY).withAddress(VALID_ADDRESS_AMY).withSubject(VALID_SUBJECT_AMY) + .withGrade(VALID_GRADE_AMY).withEducationLevel(VALID_EDUCATION_LEVEL_AMY) + .withSchool(VALID_SCHOOL_AMY).withTags().build(); + assertParseSuccess(parser, NAME_DESC_AMY + PHONE_DESC_AMY + EMAIL_DESC_AMY + ADDRESS_DESC_AMY + + SUBJECT_DESC_AMY + GRADE_DESC_AMY + EDUCATION_LEVEL_DESC_AMY + SCHOOL_DESC_AMY, + new AddTuteeCommand(expectedTutee)); + } + + @Test + public void parse_compulsoryFieldMissing_failure() { + String expectedMessage = String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddTuteeCommand.MESSAGE_USAGE); + + // missing name prefix + assertParseFailure(parser, VALID_NAME_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + ADDRESS_DESC_BOB + + SUBJECT_DESC_BOB + GRADE_DESC_BOB + EDUCATION_LEVEL_DESC_BOB + SCHOOL_DESC_BOB, + expectedMessage); + + // missing phone prefix + assertParseFailure(parser, NAME_DESC_BOB + VALID_PHONE_BOB + EMAIL_DESC_BOB + ADDRESS_DESC_BOB + + SUBJECT_DESC_BOB + GRADE_DESC_BOB + EDUCATION_LEVEL_DESC_BOB + SCHOOL_DESC_BOB, + expectedMessage); + + // missing email prefix + assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + VALID_EMAIL_BOB + ADDRESS_DESC_BOB + + SUBJECT_DESC_BOB + GRADE_DESC_BOB + EDUCATION_LEVEL_DESC_BOB + SCHOOL_DESC_BOB, + expectedMessage); + + // missing address prefix + assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + VALID_ADDRESS_BOB + + SUBJECT_DESC_BOB + GRADE_DESC_BOB + EDUCATION_LEVEL_DESC_BOB + SCHOOL_DESC_BOB, + expectedMessage); + + // all prefixes missing + assertParseFailure(parser, VALID_NAME_BOB + VALID_PHONE_BOB + VALID_EMAIL_BOB + VALID_ADDRESS_BOB + + SUBJECT_DESC_BOB + GRADE_DESC_BOB + EDUCATION_LEVEL_DESC_BOB + SCHOOL_DESC_BOB, + expectedMessage); + + // missing subject prefix + assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + ADDRESS_DESC_BOB + + GRADE_DESC_BOB + EDUCATION_LEVEL_DESC_BOB + SCHOOL_DESC_BOB, + expectedMessage); + + // missing grade prefix + assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + ADDRESS_DESC_BOB + + SUBJECT_DESC_BOB + EDUCATION_LEVEL_DESC_BOB + SCHOOL_DESC_BOB, + expectedMessage); + + // missing education level prefix + assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + ADDRESS_DESC_BOB + + SUBJECT_DESC_BOB + GRADE_DESC_BOB + SCHOOL_DESC_BOB, + expectedMessage); + + // missing school prefix + assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + ADDRESS_DESC_BOB + + SUBJECT_DESC_BOB + GRADE_DESC_BOB + EDUCATION_LEVEL_DESC_BOB, + expectedMessage); + } + + @Test + public void parse_invalidValue_failure() { + // invalid name + assertParseFailure(parser, INVALID_NAME_DESC + PHONE_DESC_BOB + EMAIL_DESC_BOB + ADDRESS_DESC_BOB + + SUBJECT_DESC_BOB + GRADE_DESC_BOB + EDUCATION_LEVEL_DESC_BOB + SCHOOL_DESC_BOB + + TAG_DESC_HUSBAND + TAG_DESC_FRIEND, Name.MESSAGE_NAME_CONSTRAINTS); + + // invalid phone + assertParseFailure(parser, NAME_DESC_BOB + INVALID_PHONE_DESC + EMAIL_DESC_BOB + ADDRESS_DESC_BOB + + SUBJECT_DESC_BOB + GRADE_DESC_BOB + EDUCATION_LEVEL_DESC_BOB + SCHOOL_DESC_BOB + + TAG_DESC_HUSBAND + TAG_DESC_FRIEND, Phone.MESSAGE_PHONE_CONSTRAINTS); + + // invalid email + assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + INVALID_EMAIL_DESC + ADDRESS_DESC_BOB + + SUBJECT_DESC_BOB + GRADE_DESC_BOB + EDUCATION_LEVEL_DESC_BOB + SCHOOL_DESC_BOB + + TAG_DESC_HUSBAND + TAG_DESC_FRIEND, Email.MESSAGE_EMAIL_CONSTRAINTS); + + // invalid address + assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + INVALID_ADDRESS_DESC + + SUBJECT_DESC_BOB + GRADE_DESC_BOB + EDUCATION_LEVEL_DESC_BOB + SCHOOL_DESC_BOB + + TAG_DESC_HUSBAND + TAG_DESC_FRIEND, Address.MESSAGE_ADDRESS_CONSTRAINTS); + + // invalid subject + assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + ADDRESS_DESC_BOB + + INVALID_SUBJECT_DESC + GRADE_DESC_BOB + EDUCATION_LEVEL_DESC_BOB + SCHOOL_DESC_BOB + + TAG_DESC_HUSBAND + TAG_DESC_FRIEND, Subject.MESSAGE_SUBJECT_CONSTRAINTS); + + // invalid grade + assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + ADDRESS_DESC_BOB + + SUBJECT_DESC_BOB + INVALID_GRADE_DESC + EDUCATION_LEVEL_DESC_BOB + SCHOOL_DESC_BOB + + TAG_DESC_HUSBAND + TAG_DESC_FRIEND, Grade.MESSAGE_GRADE_CONSTRAINTS); + + // invalid education level + assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + ADDRESS_DESC_BOB + + SUBJECT_DESC_BOB + GRADE_DESC_BOB + INVALID_EDUCATION_LEVEL + SCHOOL_DESC_BOB + + TAG_DESC_HUSBAND + TAG_DESC_FRIEND, EducationLevel.MESSAGE_EDUCATION_LEVEL_CONSTRAINTS); + + // invalid school + assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + ADDRESS_DESC_BOB + + SUBJECT_DESC_BOB + GRADE_DESC_BOB + EDUCATION_LEVEL_DESC_BOB + INVALID_SCHOOL + + TAG_DESC_HUSBAND + TAG_DESC_FRIEND, School.MESSAGE_SCHOOL_CONSTRAINTS); + + // invalid tag + assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + ADDRESS_DESC_BOB + + SUBJECT_DESC_BOB + GRADE_DESC_BOB + EDUCATION_LEVEL_DESC_BOB + SCHOOL_DESC_BOB + + INVALID_TAG_DESC + VALID_TAG_FRIEND, Tag.MESSAGE_TAG_CONSTRAINTS); + + // three invalid values, only first invalid value reported + assertParseFailure(parser, INVALID_NAME_DESC + PHONE_DESC_BOB + EMAIL_DESC_BOB + INVALID_ADDRESS_DESC + + SUBJECT_DESC_BOB + GRADE_DESC_BOB + INVALID_EDUCATION_LEVEL + SCHOOL_DESC_BOB, + Name.MESSAGE_NAME_CONSTRAINTS); + + // non-empty preamble + assertParseFailure(parser, PREAMBLE_NON_EMPTY + NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + + ADDRESS_DESC_BOB + SUBJECT_DESC_BOB + GRADE_DESC_BOB + INVALID_EDUCATION_LEVEL + + SCHOOL_DESC_BOB + TAG_DESC_HUSBAND + TAG_DESC_FRIEND, + String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddTuteeCommand.MESSAGE_USAGE)); + } +} +``` +###### \java\seedu\address\logic\parser\ChangeCommandParserTest.java +``` java +public class ChangeCommandParserTest { + private static final String DAY = "d"; + private static final String WEEK = "w"; + private static final String MONTH = "m"; + private static final String YEAR = "y"; + private ChangeCommandParser parser = new ChangeCommandParser(); + private ChangeCommand changeCommand = new ChangeCommand(DAY); // Set an initial time unit to check against + + + @Test + public void parse_validArgs_returnsChangeCommand() throws Exception { + // get the initial time unit, d + String initialTimeUnit = ChangeCommand.getTimeUnit(); + + // Change time unit to w + ChangeCommand expectedTimeUnit = new ChangeCommand(WEEK); + ChangeCommand changeToInitialTimeUnit = new ChangeCommand(initialTimeUnit); // Change to initial time unit + assertEquals(expectedTimeUnit, parser.parse(WEEK)); + + // Change time unit to m + expectedTimeUnit = new ChangeCommand(MONTH); + changeToInitialTimeUnit = new ChangeCommand(initialTimeUnit); // Change to initial time unit + assertEquals(expectedTimeUnit, parser.parse(MONTH)); + + // Change time unit to y + expectedTimeUnit = new ChangeCommand(YEAR); + changeToInitialTimeUnit = new ChangeCommand(initialTimeUnit); // Change to initial time unit + assertEquals(expectedTimeUnit, parser.parse(YEAR)); + } + + @Test + public void parse_invalidArgs_throwsParseException() { + assertParseFailure(parser, "a", String.format(MESSAGE_INVALID_COMMAND_FORMAT, ChangeCommand.MESSAGE_USAGE)); + assertParseFailure(parser, "D", String.format(MESSAGE_INVALID_COMMAND_FORMAT, ChangeCommand.MESSAGE_USAGE)); + assertParseFailure(parser, "@", String.format(MESSAGE_INVALID_COMMAND_FORMAT, ChangeCommand.MESSAGE_USAGE)); + assertParseFailure(parser, "1", String.format(MESSAGE_INVALID_COMMAND_FORMAT, ChangeCommand.MESSAGE_USAGE)); + assertParseFailure(parser, "", String.format(MESSAGE_INVALID_COMMAND_FORMAT, ChangeCommand.MESSAGE_USAGE)); + } + + @Test + public void parse_sameViewPageTimeUnit_throwsSameTimeUnitException() { + assertParseFailure(parser, "d", String.format(ChangeCommand.MESSAGE_SAME_VIEW)); + } + + @Before + public void isValidTimeUnit() { + // null time unit + Assert.assertThrows(NullPointerException.class, () -> ChangeCommandParser.isValidTimeUnit(null)); + + // invalid time unit + assertFalse(ChangeCommandParser.isValidTimeUnit("")); // empty string + assertFalse(ChangeCommandParser.isValidTimeUnit(" ")); // space only + assertFalse(ChangeCommandParser.isValidTimeUnit("#")); // special characters + assertFalse(ChangeCommandParser.isValidTimeUnit("day")); // full time unit name + assertFalse(ChangeCommandParser.isValidTimeUnit("1")); // numbers + assertFalse(ChangeCommandParser.isValidTimeUnit("a")); // contains invalid alphabets + assertFalse(ChangeCommandParser.isValidTimeUnit("D")); // Capital + assertFalse(ChangeCommandParser.isValidTimeUnit(" d ")); // contains space + + // valid time unit + assertTrue(ChangeCommandParser.isValidTimeUnit(DAY)); // day + assertTrue(ChangeCommandParser.isValidTimeUnit(WEEK)); // week + assertTrue(ChangeCommandParser.isValidTimeUnit(MONTH)); // month + assertTrue(ChangeCommandParser.isValidTimeUnit(YEAR)); // year + } + + @Test + public void isTimeUnitClash() { + // All time units' validity are checked in isValidTimeUnit() + + // There is a clash of time unit + assertTrue(ChangeCommandParser.isTimeUnitClash("d")); + + // No clash in time unit + assertFalse(ChangeCommandParser.isTimeUnitClash("w")); + assertFalse(ChangeCommandParser.isTimeUnitClash("m")); + assertFalse(ChangeCommandParser.isTimeUnitClash("y")); + + // change current time unit to w + changeCommand = new ChangeCommand("w"); + + // There is a clash of time unit for w now + assertTrue(ChangeCommandParser.isTimeUnitClash("w")); + + // d is no longer clash + assertFalse(ChangeCommandParser.isTimeUnitClash("d")); + } +} +``` +###### \java\seedu\address\logic\parser\EditCommandParserTest.java +``` java + // subject + userInput = targetIndex.getOneBased() + SUBJECT_DESC_AMY; + descriptor = new EditPersonDescriptorBuilder().withSubject(VALID_SUBJECT_AMY).build(); + expectedCommand = new EditCommand(targetIndex, descriptor); + assertParseSuccess(parser, userInput, expectedCommand); + + // grade + userInput = targetIndex.getOneBased() + GRADE_DESC_AMY; + descriptor = new EditPersonDescriptorBuilder().withGrade(VALID_GRADE_AMY).build(); + expectedCommand = new EditCommand(targetIndex, descriptor); + assertParseSuccess(parser, userInput, expectedCommand); + + // education level + userInput = targetIndex.getOneBased() + EDUCATION_LEVEL_DESC_AMY; + descriptor = new EditPersonDescriptorBuilder().withEducationLevel(VALID_EDUCATION_LEVEL_AMY).build(); + expectedCommand = new EditCommand(targetIndex, descriptor); + assertParseSuccess(parser, userInput, expectedCommand); + + // school + userInput = targetIndex.getOneBased() + SCHOOL_DESC_AMY; + descriptor = new EditPersonDescriptorBuilder().withSchool(VALID_SCHOOL_AMY).build(); + expectedCommand = new EditCommand(targetIndex, descriptor); + assertParseSuccess(parser, userInput, expectedCommand); +``` +###### \java\seedu\address\logic\parser\ParserUtilTest.java +``` java + @Test + public void parseTags_collectionWithValidTagsAndTuteeTag_returnsTagSet() throws Exception { + Set actualTagSet = ParserUtil.parseTags(Arrays.asList(VALID_TAG_1, CAPITAL_TUTEE_TAG)); + Set expectedTagSet = new HashSet<>(Arrays.asList(new Tag(VALID_TAG_1), new Tag(TUTEE_TAG))); + + assertEquals(expectedTagSet, actualTagSet); + } + + @Test + public void parsePersonTags_null_throwsNullPointerException() throws Exception { + thrown.expect(NullPointerException.class); + ParserUtil.parsePersonTags(null); + } + + @Test + public void parsePersonTags_collectionWithInvalidTags_throwsIllegalValueException() throws Exception { + thrown.expect(IllegalValueException.class); + ParserUtil.parsePersonTags(Arrays.asList(VALID_TAG_1, INVALID_TAG)); + } + + @Test + public void parsePersonTags_emptyCollection_returnsEmptySet() throws Exception { + assertTrue(ParserUtil.parsePersonTags(Collections.emptyList()).isEmpty()); + } + + @Test + public void parsePersonTags_collectionWithValidTags_returnsTagSet() throws Exception { + Set actualTagSet = ParserUtil.parsePersonTags(Arrays.asList(VALID_TAG_1, VALID_TAG_2)); + Set expectedTagSet = new HashSet<>(Arrays.asList(new Tag(VALID_TAG_1), new Tag(VALID_TAG_2))); + + assertEquals(expectedTagSet, actualTagSet); + } + + @Test + public void parsePersonTags_collectionWithTuteeTags_throwsIllegalValueException() throws Exception { + thrown.expect(IllegalValueException.class); + ParserUtil.parsePersonTags(Arrays.asList(VALID_TAG_1, TUTEE_TAG)); + } + + @Test + public void parseTuteeTags_null_throwsNullPointerException() throws Exception { + thrown.expect(NullPointerException.class); + ParserUtil.parseTuteeTags(null); + } + + @Test + public void parseTuteeTags_collectionWithInvalidTags_throwsIllegalValueException() throws Exception { + thrown.expect(IllegalValueException.class); + ParserUtil.parseTuteeTags(Arrays.asList(VALID_TAG_1, INVALID_TAG)); + } + + @Test + public void parseTuteeTags_emptyCollection_returnsEmptySet() throws Exception { + assertTrue(ParserUtil.parseTuteeTags(Collections.emptyList()).isEmpty()); + } + + @Test + public void parseTuteeTags_collectionWithValidTags_returnsTagSet() throws Exception { + Set actualTagSet = ParserUtil.parseTuteeTags(Arrays.asList(VALID_TAG_1, VALID_TAG_2)); + Set expectedTagSet = new HashSet<>(Arrays.asList(new Tag(VALID_TAG_1), new Tag(VALID_TAG_2))); + + assertEquals(expectedTagSet, actualTagSet); + } + + @Test + public void parseTuteeTags_collectionWithValidAndTuteeTags_returnsTagSet() throws Exception { + Set actualTagSet = ParserUtil.parseTuteeTags(Arrays.asList(VALID_TAG_1, TUTEE_TAG)); + Set expectedTagSet = new HashSet<>(Arrays.asList(new Tag(VALID_TAG_1))); + + assertEquals(expectedTagSet, actualTagSet); + } + + @Test + public void parseSubject_null_throwsNullPointerException() { + Assert.assertThrows(NullPointerException.class, () -> ParserUtil.parseSubject((String) null)); + Assert.assertThrows(NullPointerException.class, () -> ParserUtil.parseSubject((Optional) null)); + } + + @Test + public void parseSubject_invalidValue_throwsIllegalValueException() { + Assert.assertThrows(IllegalValueException.class, () -> ParserUtil.parseSubject(INVALID_SUBJECT)); + Assert.assertThrows(IllegalValueException.class, () -> ParserUtil.parseSubject(Optional.of(INVALID_SUBJECT))); + } + + @Test + public void parseSubject_optionalEmpty_returnsOptionalEmpty() throws Exception { + assertFalse(ParserUtil.parseSubject(Optional.empty()).isPresent()); + } + + @Test + public void parseSubject_validValueWithoutWhitespace_returnsSubject() throws Exception { + Subject expectedSubject = new Subject(VALID_SUBJECT); + assertEquals(expectedSubject, ParserUtil.parseSubject(VALID_SUBJECT)); + assertEquals(Optional.of(expectedSubject), ParserUtil.parseSubject(Optional.of(VALID_SUBJECT))); + } + + @Test + public void parseSubject_validValueWithWhitespace_returnsTrimmedSubject() throws Exception { + String subjectWithWhitespace = WHITESPACE + VALID_SUBJECT + WHITESPACE; + Subject expectedSubject = new Subject(VALID_SUBJECT); + assertEquals(expectedSubject, ParserUtil.parseSubject(subjectWithWhitespace)); + assertEquals(Optional.of(expectedSubject), ParserUtil.parseSubject(Optional.of(subjectWithWhitespace))); + } + + @Test + public void parseGrade_null_throwsNullPointerException() { + Assert.assertThrows(NullPointerException.class, () -> ParserUtil.parseGrade((String) null)); + Assert.assertThrows(NullPointerException.class, () -> ParserUtil.parseGrade((Optional) null)); + } + + @Test + public void parseGrade_invalidValue_throwsIllegalValueException() { + Assert.assertThrows(IllegalValueException.class, () -> ParserUtil.parseGrade(INVALID_GRADE)); + Assert.assertThrows(IllegalValueException.class, () -> ParserUtil.parseGrade(Optional.of(INVALID_GRADE))); + } + + @Test + public void parseGrade_optionalEmpty_returnsOptionalEmpty() throws Exception { + assertFalse(ParserUtil.parseGrade(Optional.empty()).isPresent()); + } + + @Test + public void parseGrade_validValueWithoutWhitespace_returnsGrade() throws Exception { + Grade expectedGrade = new Grade(VALID_GRADE); + assertEquals(expectedGrade, ParserUtil.parseGrade(VALID_GRADE)); + assertEquals(Optional.of(expectedGrade), ParserUtil.parseGrade(Optional.of(VALID_GRADE))); + } + + @Test + public void parseGrade_validValueWithWhitespace_returnsTrimmedGrade() throws Exception { + String gradeWithWhitespace = WHITESPACE + VALID_GRADE + WHITESPACE; + Grade expectedGrade = new Grade(VALID_GRADE); + assertEquals(expectedGrade, ParserUtil.parseGrade(gradeWithWhitespace)); + assertEquals(Optional.of(expectedGrade), ParserUtil.parseGrade(Optional.of(gradeWithWhitespace))); + } + + @Test + public void parseEducationLevel_null_throwsNullPointerException() { + Assert.assertThrows(NullPointerException.class, () -> ParserUtil.parseEducationLevel((String) null)); + Assert.assertThrows(NullPointerException.class, () -> ParserUtil.parseEducationLevel((Optional) null)); + } + + @Test + public void parseEducationLevel_invalidValue_throwsIllegalValueException() { + Assert.assertThrows(IllegalValueException.class, () -> ParserUtil.parseEducationLevel( + INVALID_EDUCATIONAL_LEVEL)); + Assert.assertThrows(IllegalValueException.class, () -> ParserUtil.parseEducationLevel( + Optional.of(INVALID_EDUCATIONAL_LEVEL))); + } + + @Test + public void parseEducationLevel_optionalEmpty_returnsOptionalEmpty() throws Exception { + assertFalse(ParserUtil.parseEducationLevel(Optional.empty()).isPresent()); + } + + @Test + public void parseEducationLevel_validValueWithoutWhitespace_returnsEducationLevel() throws Exception { + EducationLevel expectedEducationLevel = new EducationLevel(VALID_EDUCATIONAL_LEVEL); + assertEquals(expectedEducationLevel, ParserUtil.parseEducationLevel(VALID_EDUCATIONAL_LEVEL)); + assertEquals(Optional.of(expectedEducationLevel), ParserUtil.parseEducationLevel( + Optional.of(VALID_EDUCATIONAL_LEVEL))); + } + + @Test + public void parseEducationLevel_validValueWithWhitespace_returnsTrimmedEducationLevel() throws Exception { + String educationLevelWithWhitespace = WHITESPACE + VALID_EDUCATIONAL_LEVEL + WHITESPACE; + EducationLevel expectedEducationLevel = new EducationLevel(VALID_EDUCATIONAL_LEVEL); + assertEquals(expectedEducationLevel, ParserUtil.parseEducationLevel(educationLevelWithWhitespace)); + assertEquals(Optional.of(expectedEducationLevel), ParserUtil.parseEducationLevel( + Optional.of(educationLevelWithWhitespace))); + } + + @Test + public void parseSchool_null_throwsNullPointerException() { + Assert.assertThrows(NullPointerException.class, () -> ParserUtil.parseSchool((String) null)); + Assert.assertThrows(NullPointerException.class, () -> ParserUtil.parseSchool((Optional) null)); + } + + @Test + public void parseSchool_invalidValue_throwsIllegalValueException() { + Assert.assertThrows(IllegalValueException.class, () -> ParserUtil.parseSchool(INVALID_SCHOOL)); + Assert.assertThrows(IllegalValueException.class, () -> ParserUtil.parseSchool(Optional.of(INVALID_SCHOOL))); + } + + @Test + public void parseSchool_optionalEmpty_returnsOptionalEmpty() throws Exception { + assertFalse(ParserUtil.parseSchool(Optional.empty()).isPresent()); + } + + @Test + public void parseSchool_validValueWithoutWhitespace_returnsSchool() throws Exception { + School expectedSchool = new School(VALID_SCHOOL); + assertEquals(expectedSchool, ParserUtil.parseSchool(VALID_SCHOOL)); + assertEquals(Optional.of(expectedSchool), ParserUtil.parseSchool(Optional.of(VALID_SCHOOL))); + } + + @Test + public void parseSchool_validValueWithWhitespace_returnsTrimmedSchool() throws Exception { + String schoolWithWhitespace = WHITESPACE + VALID_SCHOOL + WHITESPACE; + School expectedSchool = new School(VALID_SCHOOL); + assertEquals(expectedSchool, ParserUtil.parseSchool(schoolWithWhitespace)); + assertEquals(Optional.of(expectedSchool), ParserUtil.parseSchool(Optional.of(schoolWithWhitespace))); + } + + @Test + public void parseTimeUnit_null_throwsNullPointerException() { + Assert.assertThrows(NullPointerException.class, () -> ParserUtil.parseTimeUnit(null)); + } + + @Test + public void parseTimeUnit_invalidValue_throwsIllegalValueException() { + Assert.assertThrows(IllegalValueException.class, () -> ParserUtil.parseTimeUnit(INVALID_TIME_UNIT)); + } + + @Test + public void parseTimeUnit_validValueWithoutWhitespace_returnsTimeUnit() throws Exception { + String expectedTimeUnit = VALID_TIME_UNIT; + assertEquals(expectedTimeUnit, ParserUtil.parseTimeUnit(VALID_TIME_UNIT)); + } + + @Test + public void parseTimeUnit_validValueWithWhitespace_returnsTrimmedTimeUnit() throws Exception { + String timeUnitWithWhitespace = WHITESPACE + VALID_TIME_UNIT + WHITESPACE; + assertEquals(VALID_TIME_UNIT, ParserUtil.parseTimeUnit(timeUnitWithWhitespace)); + } + +``` +###### \java\seedu\address\model\personal\PersonalTaskTest.java +``` java +public class PersonalTaskTest { + + @BeforeClass + public static void setupBeforeClass() { + new TypicalCalendarEntries(); + } + + @Test + public void constructor_validArgs_success() { + PersonalTask personalTask = new PersonalTask(VALID_START_DATE_TIME, + VALID_DURATION, VALID_DESCRIPTION); + Entry actualEntry = personalTask.getEntry(); + Entry expectedEntry = getPersonalEntry(); + + // To match the ID of the same entry + actualEntry.setId("0"); + expectedEntry.setId("0"); + + assertEquals(VALID_START_DATE_TIME, personalTask.getTaskDateTime()); + assertEquals(VALID_DURATION, personalTask.getDuration()); + assertEquals(VALID_DESCRIPTION, personalTask.getDescription()); + assertEquals(expectedEntry, actualEntry); + } +} +``` +###### \java\seedu\address\model\tutee\EducationLevelTest.java +``` java +public class EducationLevelTest { + + @Test + public void constructor_null_throwsNullPointerException() { + Assert.assertThrows(NullPointerException.class, () -> new EducationLevel(null)); + } + + @Test + public void constructor_invalidEducationLevel_throwsIllegalArgumentException() { + String invalidEducationLevel = ""; + Assert.assertThrows(IllegalArgumentException.class, () -> new EducationLevel(invalidEducationLevel)); + } + + @Test + public void isValidEducationLevel() { + // null education level + Assert.assertThrows(NullPointerException.class, () -> EducationLevel.isValidEducationLevel(null)); + + // invalid education level + assertFalse(EducationLevel.isValidEducationLevel("")); // empty string + assertFalse(EducationLevel.isValidEducationLevel(" ")); // spaces only + assertFalse(EducationLevel.isValidEducationLevel("91")); // numbers + assertFalse(EducationLevel.isValidEducationLevel("university")); // not the specified education level + assertFalse(EducationLevel.isValidEducationLevel("primary5")); // contains number + assertFalse(EducationLevel.isValidEducationLevel("primary@")); // contains special characters + assertFalse(EducationLevel.isValidEducationLevel(" secondary ")); // multiple leading and trailing whitespaces + + // valid education level + assertTrue(EducationLevel.isValidEducationLevel("primary")); // primary school + assertTrue(EducationLevel.isValidEducationLevel("secondary")); // secondary school + assertTrue(EducationLevel.isValidEducationLevel("junior college")); // junior college + assertTrue(EducationLevel.isValidEducationLevel("SeCoNdaRy")); // Capital + } +} +``` +###### \java\seedu\address\model\tutee\GradeTest.java +``` java +public class GradeTest { + + @Test + public void constructor_null_throwsNullPointerException() { + Assert.assertThrows(NullPointerException.class, () -> new Grade(null)); + } + + @Test + public void constructor_invalidGrade_throwsIllegalArgumentException() { + String invalidGrade = ""; + Assert.assertThrows(IllegalArgumentException.class, () -> new Grade(invalidGrade)); + } + + @Test + public void isValidGrade() { + // null grade + Assert.assertThrows(NullPointerException.class, () -> Grade.isValidGrade(null)); + + // invalid grade + assertFalse(Grade.isValidGrade("")); // empty string + assertFalse(Grade.isValidGrade(" ")); // spaces only + assertFalse(Grade.isValidGrade("9112")); // only contains numbers + assertFalse(Grade.isValidGrade("pass")); // more than 2 alphabet + assertFalse(Grade.isValidGrade("+B")); // special character before alphabet + assertFalse(Grade.isValidGrade("B -")); // spaces within digits + assertFalse(Grade.isValidGrade(" B")); // leading whitespace + + // valid grade + assertTrue(Grade.isValidGrade("A+")); // 1 alphabet followed by a special character + assertTrue(Grade.isValidGrade("B")); // only 1 alphabet + assertTrue(Grade.isValidGrade("b")); // small letter + assertTrue(Grade.isValidGrade("C5")); // number after alphabet + } + +} +``` +###### \java\seedu\address\model\tutee\SchoolTest.java +``` java +public class SchoolTest { + + @Test + public void constructor_null_throwsNullPointerException() { + Assert.assertThrows(NullPointerException.class, () -> new School(null)); + } + + @Test + public void constructor_invalidSchool_throwsIllegalArgumentException() { + String invalidSchool = ""; + Assert.assertThrows(IllegalArgumentException.class, () -> new School(invalidSchool)); + } + + @Test + public void isValidSchool() { + // null school name + Assert.assertThrows(NullPointerException.class, () -> School.isValidSchool(null)); + + // invalid school name + assertFalse(School.isValidSchool("")); // empty string + assertFalse(School.isValidSchool(" ")); // spaces only + assertFalse(School.isValidSchool("^")); // only non-alphabetic characters + assertFalse(School.isValidSchool("bedok primary school*")); // contains non-alphabetic characters + assertFalse(School.isValidSchool("911")); // numbers only + assertFalse(School.isValidSchool("bedok12 secondary school")); // contains numbers + + // valid school name + assertTrue(School.isValidSchool("victoria junior college")); // alphabets only + assertTrue(School.isValidSchool("Victoria Junior College")); // with capital letters + assertTrue(School.isValidSchool("The longest name school primary school")); // long school name + } + +} +``` +###### \java\seedu\address\model\tutee\SubjectTest.java +``` java +public class SubjectTest { + + @Test + public void constructor_null_throwsNullPointerException() { + Assert.assertThrows(NullPointerException.class, () -> new Subject(null)); + } + + @Test + public void constructor_invalidSubject_throwsIllegalArgumentException() { + String invalidSubject = ""; + Assert.assertThrows(IllegalArgumentException.class, () -> new Subject(invalidSubject)); + } + + @Test + public void isValidSubject() { + // null subject + Assert.assertThrows(NullPointerException.class, () -> Subject.isValidSubject(null)); + + // invalid subject name + assertFalse(Subject.isValidSubject("")); // empty string + assertFalse(Subject.isValidSubject(" ")); // spaces only + assertFalse(Subject.isValidSubject("^")); // only non-alphabetic characters + assertFalse(Subject.isValidSubject("economics*")); // contains non-alphabetic characters + assertFalse(Subject.isValidSubject("911")); // numbers only + assertFalse(Subject.isValidSubject("math12")); // contains numbers + + // valid subject name + assertTrue(Subject.isValidSubject("social studies")); // alphabets only + assertTrue(Subject.isValidSubject("Social Studies")); // with capital letters + assertTrue(Subject.isValidSubject("introduction to fluid dynamics")); // long subject name + } + +} +``` +###### \java\seedu\address\model\tutee\TuitionTaskTest.java +``` java +public class TuitionTaskTest { + + @BeforeClass + public static void setupBeforeClass() { + new TypicalCalendarEntries(); + } + + @Test + public void constructor_validArgs_success() { + TuitionTask tuitionTask = new TuitionTask(VALID_NAME, VALID_START_DATE_TIME, + VALID_DURATION, VALID_DESCRIPTION); + Entry actualEntry = tuitionTask.getEntry(); + Entry expectedEntry = getTuitionEntry(); + + // To match the ID of the same entry + actualEntry.setId("0"); + expectedEntry.setId("0"); + + assertEquals(VALID_NAME, tuitionTask.getPerson()); + assertEquals(VALID_START_DATE_TIME, tuitionTask.getTaskDateTime()); + assertEquals(VALID_DURATION, tuitionTask.getDuration()); + assertEquals(VALID_DESCRIPTION, tuitionTask.getDescription()); + assertEquals(expectedEntry, actualEntry); + } +} +``` +###### \java\seedu\address\model\UniqueTaskListTest.java +``` java +public class UniqueTaskListTest { + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + private DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd/MM/uuuu HH:mm") + .withResolverStyle(ResolverStyle.STRICT); + private UniqueTaskList uniqueTaskList = new UniqueTaskList(); + + @BeforeClass + public static void setupBeforeClass() { + SystemTestSetupHelper.initialize(); + } + + @Test + public void asObservableList_modifyList_throwsUnsupportedOperationException() { + UniqueTaskList uniqueTaskList = new UniqueTaskList(); + thrown.expect(UnsupportedOperationException.class); + uniqueTaskList.asObservableList().remove(0); + } + + @Test + public void addNewTask_clashes_throwsTimingClashException() { + try { + createTaskList(); + } catch (TimingClashException e) { + throw new AssertionError("Should not have any clashed timing"); + } + // New task starts at the same time as an existing task + Assert.assertThrows(TimingClashException.class, () -> + uniqueTaskList.add(new PersonalTask( + LocalDateTime.parse("11/01/2011 22:00", formatter), "2h0m", "Homework 1"))); + + // New task starts during an existing task + Assert.assertThrows(TimingClashException.class, () -> + uniqueTaskList.add(new PersonalTask( + LocalDateTime.parse("15/01/2011 22:30", formatter), "2h0m", "Homework 2"))); + + // New task ends at the same time as an existing task + Assert.assertThrows(TimingClashException.class, () -> + uniqueTaskList.add(new PersonalTask( + LocalDateTime.parse("13/01/2011 11:30", formatter), "0h30m", "Homework 3"))); + + // New task ends during an existing task + Assert.assertThrows(TimingClashException.class, () -> + uniqueTaskList.add(new PersonalTask( + LocalDateTime.parse("13/01/2011 10:00", formatter), "1h30m", "Homework 4"))); + + // New task is within an existing task completely + Assert.assertThrows(TimingClashException.class, () -> + uniqueTaskList.add(new TuitionTask( + "Anne", LocalDateTime.parse("15/01/2011 22:30", formatter), "1h30m", "Assignment"))); + + // Existing task is within the new task completely + Assert.assertThrows(TimingClashException.class, () -> + uniqueTaskList.add(new TuitionTask( + "Ben", LocalDateTime.parse("11/01/2011 21:00", formatter), "4h0m", "Revision"))); + } + + /** + * Generates a list of existing tasks + */ + private void createTaskList() throws TimingClashException { + uniqueTaskList.add(new TuitionTask("Anne", + LocalDateTime.parse("11/01/2011 22:00", formatter), "1h30m", "tuition 1")); + uniqueTaskList.add(new PersonalTask( + LocalDateTime.parse("15/01/2011 22:00", formatter), "2h30m", "personal task 1")); + uniqueTaskList.add(new PersonalTask( + LocalDateTime.parse("13/01/2011 11:00", formatter), "1h0m", "personal task 2")); + } +} +``` +###### \java\seedu\address\testutil\EditPersonDescriptorBuilder.java +``` java + /** + * Returns an {@code EditPersonDescriptor} with fields containing {@code tutee}'s details + */ + public EditPersonDescriptorBuilder(Tutee tutee) { + descriptor = new EditPersonDescriptor(); + descriptor.setName(tutee.getName()); + descriptor.setPhone(tutee.getPhone()); + descriptor.setEmail(tutee.getEmail()); + descriptor.setAddress(tutee.getAddress()); + descriptor.setSubject(tutee.getSubject()); + descriptor.setGrade(tutee.getGrade()); + descriptor.setEducationLevel(tutee.getEducationLevel()); + descriptor.setSchool(tutee.getSchool()); + descriptor.setTags(tutee.getTags()); + } +``` +###### \java\seedu\address\testutil\EditPersonDescriptorBuilder.java +``` java + /** + * Sets the {@code Subject} of the {@code EditPersonDescriptor} that we are building. + */ + public EditPersonDescriptorBuilder withSubject(String subject) { + descriptor.setSubject(new Subject(subject)); + return this; + } + + /** + * Sets the {@code Grade} of the {@code EditPersonDescriptor} that we are building. + */ + public EditPersonDescriptorBuilder withGrade(String grade) { + descriptor.setGrade(new Grade(grade)); + return this; + } + + + /** + * Sets the {@code EducationLevel} of the {@code EditPersonDescriptor} that we are building. + */ + public EditPersonDescriptorBuilder withEducationLevel(String educationLevel) { + descriptor.setEducationLevel(new EducationLevel(educationLevel)); + return this; + } + + + /** + * Sets the {@code School} of the {@code EditPersonDescriptor} that we are building. + */ + public EditPersonDescriptorBuilder withSchool(String school) { + descriptor.setSchool(new School(school)); + return this; + } +``` +###### \java\seedu\address\testutil\TuteeBuilder.java +``` java +/** + * A utility class to help with building Tutee objects. + */ +public class TuteeBuilder extends PersonBuilder { + public static final String DEFAULT_SUBJECT = "mathematics"; + public static final String DEFAULT_GRADE = "C+"; + public static final String DEFAULT_EDUCATION_LEVEL = "secondary"; + public static final String DEFAULT_SCHOOL = "fengshan secondary school"; + public static final String TUTEE_TAG = "Tutee"; + + private Subject subject; + private Grade grade; + private EducationLevel educationLevel; + private School school; + + public TuteeBuilder() { + name = new Name(DEFAULT_NAME); + phone = new Phone(DEFAULT_PHONE); + email = new Email(DEFAULT_EMAIL); + address = new Address(DEFAULT_ADDRESS); + subject = new Subject(DEFAULT_SUBJECT); + grade = new Grade(DEFAULT_GRADE); + educationLevel = new EducationLevel(DEFAULT_EDUCATION_LEVEL); + school = new School(DEFAULT_SCHOOL); + tags = SampleDataUtil.getTagSet(DEFAULT_TAGS); + tags.add(new Tag(TUTEE_TAG)); + } + + /** + * Initializes the PersonBuilder with the data of {@code personToCopy}. + */ + public TuteeBuilder(Tutee tuteeToCopy) { + name = tuteeToCopy.getName(); + phone = tuteeToCopy.getPhone(); + email = tuteeToCopy.getEmail(); + address = tuteeToCopy.getAddress(); + subject = tuteeToCopy.getSubject(); + grade = tuteeToCopy.getGrade(); + educationLevel = tuteeToCopy.getEducationLevel(); + school = tuteeToCopy.getSchool(); + tags = new HashSet<>(tuteeToCopy.getTags()); + } + + /** + * Sets the {@code Name} of the {@code Tutee} that we are building. + */ + public TuteeBuilder withName(String name) { + this.name = new Name(name); + return this; + } + + /** + * Parses the {@code tags} into a {@code Set} and set it to the {@code Tutee} that we are building. + */ + public TuteeBuilder withTags(String ... tags) { + this.tags = SampleDataUtil.getTagSet(tags); + this.tags.add(new Tag("Tutee")); + return this; + } + + /** + * Sets the {@code Address} of the {@code Tutee} that we are building. + */ + public TuteeBuilder withAddress(String address) { + this.address = new Address(address); + return this; + } + + /** + * Sets the {@code Phone} of the {@code Tutee} that we are building. + */ + public TuteeBuilder withPhone(String phone) { + this.phone = new Phone(phone); + return this; + } + + /** + * Sets the {@code Email} of the {@code Tutee} that we are building. + */ + public TuteeBuilder withEmail(String email) { + this.email = new Email(email); + return this; + } + + /** + * Sets the {@code Subject} of the {@code Tutee} that we are building. + */ + public TuteeBuilder withSubject(String subject) { + this.subject = new Subject(subject); + return this; + } + /** + * Sets the {@code Grade} of the {@code Tutee} that we are building. + */ + public TuteeBuilder withGrade(String grade) { + this.grade = new Grade(grade); + return this; + } + /** + * Sets the {@code EducationLevel} of the {@code Tutee} that we are building. + */ + public TuteeBuilder withEducationLevel(String educationLevel) { + this.educationLevel = new EducationLevel(educationLevel); + return this; + } + /** + * Sets the {@code School} of the {@code Tutee} that we are building. + */ + public TuteeBuilder withSchool(String school) { + this.school = new School(school); + return this; + } + + + public Tutee build() { + return new Tutee(name, phone, email, address, subject, grade, educationLevel, school, tags); + } + +} +``` +###### \java\seedu\address\testutil\TuteeUtil.java +``` java +/** + * A utility class for Tutee. + */ +public class TuteeUtil { + + /** + * Returns an addtutee command string for adding the {@code tutee}. + */ + public static String getAddTuteeCommand(Tutee tutee) { + return AddTuteeCommand.COMMAND_WORD + " " + getTuteeDetails(tutee); + } + + /** + * Returns the part of command string for the given {@code tutee}'s details. + */ + public static String getTuteeDetails(Tutee tutee) { + StringBuilder sb = new StringBuilder(); + sb.append(PREFIX_NAME + tutee.getName().fullName + " "); + sb.append(PREFIX_PHONE + tutee.getPhone().value + " "); + sb.append(PREFIX_EMAIL + tutee.getEmail().value + " "); + sb.append(PREFIX_ADDRESS + tutee.getAddress().value + " "); + sb.append(PREFIX_SUBJECT + tutee.getSubject().subject + " "); + sb.append(PREFIX_GRADE + tutee.getGrade().grade + " "); + sb.append(PREFIX_EDUCATION_LEVEL + tutee.getEducationLevel().educationLevel + " "); + sb.append(PREFIX_SCHOOL + tutee.getSchool().school + " "); + tutee.getTags().stream().forEach( + s -> sb.append(PREFIX_TAG + s.tagName + " ") + ); + return sb.toString(); + } +} +``` +###### \java\seedu\address\testutil\typicaladdressbook\TypicalTutees.java +``` java +/** + * A utility class containing a list of {@code Tutee} objects to be used in tests. + */ +public class TypicalTutees { + + // Manually added + public static final Tutee ALICETUTEE = new TuteeBuilder().withName("Alice Pauline") + .withAddress("123, Jurong West Ave 6, #08-111").withEmail("alice@gmail.com") + .withPhone("85355255").withSubject("mathematics").withGrade("C+").withEducationLevel("secondary") + .withSchool("fengshan secondary school").withTags("friends").build(); + + public static final Tutee CARLTUTEE = new TuteeBuilder().withName("Carl Kurz").withPhone("95352563") + .withEmail("heinz@example.com").withAddress("wall street").withSubject("history").withGrade("B") + .withEducationLevel("secondary").withSchool("wall street high school").build(); + + public static final Tutee HOONTUTEE = new TuteeBuilder().withName("Hoon Meier").withPhone("8482424") + .withEmail("stefan@example.com").withAddress("little india").withSubject("economics").withGrade("A1") + .withEducationLevel("secondary").withSchool("changi secondary school").build(); + + public static final Tutee IDATUTEE = new TuteeBuilder().withName("Ida Mueller").withPhone("8482131") + .withEmail("hans@example.com").withAddress("chicago ave").withSubject("english").withGrade("B3") + .withEducationLevel("secondary").withSchool("tanjong katong secondary school").build(); + + // Manually added - Tutee's details found in {@code CommandTestUtil} + public static final Tutee AMYTUTEE = new TuteeBuilder().withName(VALID_NAME_AMY).withPhone(VALID_PHONE_AMY) + .withEmail(VALID_EMAIL_AMY).withAddress(VALID_ADDRESS_AMY).withSubject(VALID_SUBJECT_AMY) + .withGrade(VALID_GRADE_AMY).withEducationLevel(VALID_EDUCATION_LEVEL_AMY).withSchool(VALID_SCHOOL_AMY) + .withTags(VALID_TAG_FRIEND).build(); + + public static final Tutee BOBTUTEE = new TuteeBuilder().withName(VALID_NAME_BOB).withPhone(VALID_PHONE_BOB) + .withEmail(VALID_EMAIL_BOB).withAddress(VALID_ADDRESS_BOB).withSubject(VALID_SUBJECT_BOB) + .withGrade(VALID_GRADE_BOB).withEducationLevel(VALID_EDUCATION_LEVEL_BOB).withSchool(VALID_SCHOOL_BOB) + .withTags(VALID_TAG_HUSBAND, VALID_TAG_FRIEND).build(); + + private TypicalTutees() {} // prevents instantiation + + public static List getTypicalPersonsAndTutees() { + + return new ArrayList<>(Arrays.asList(ALICETUTEE, DANIEL, AMYTUTEE, BOBTUTEE)); + } +} +``` +###### \java\seedu\address\testutil\TypicalCalendarEntries.java +``` java +/** + * A utility class containing a list of {@code Entry} objects to be used in tests. + */ +public class TypicalCalendarEntries { + public static final String VALID_NAME = "Jason"; + public static final String VALID_DURATION = "1h30m"; + public static final String VALID_DESCRIPTION = "homework 1"; + private static final String VALID_STRING_START_DATE_TIME = "01/04/2018 11:00"; + private static final String VALID_STRING_END_DATE_TIME = "01/04/2018 12:30"; + + private static DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd/MM/uuuu HH:mm") + .withResolverStyle(ResolverStyle.STRICT); + public static final LocalDateTime VALID_START_DATE_TIME = + LocalDateTime.parse(VALID_STRING_START_DATE_TIME, formatter); + public static final LocalDateTime VALID_END_DATE_TIME = + LocalDateTime.parse(VALID_STRING_END_DATE_TIME, formatter); + + private static Entry validTuitionEntry; + private static Entry validPersonalEntry; + + /** + * Creates valid calendar entry + * + */ + public TypicalCalendarEntries() { + Interval interval = new Interval(VALID_START_DATE_TIME, VALID_END_DATE_TIME); + createTuitionEntry(interval); + createPersonalEntry(interval); + } + + /** + * Creates a valid tuition calendar entry + * + * @param interval of the entry + */ + private void createTuitionEntry(Interval interval) { + validTuitionEntry = new Entry(VALID_NAME); + validTuitionEntry.setInterval(interval); + } + + /** + * Creates a valid personal calendar entry + * + * @param interval of the entry + */ + private void createPersonalEntry(Interval interval) { + validPersonalEntry = new Entry(VALID_DESCRIPTION); + validPersonalEntry.setInterval(interval); + } + + public static Entry getTuitionEntry() { + return validTuitionEntry; + } + + public static Entry getPersonalEntry() { + return validPersonalEntry; + } +} +``` +###### \java\seedu\address\ui\CalendarPanelTest.java +``` java +public class CalendarPanelTest extends GuiUnitTest { + + private CalendarPanel calendarPanel; + private CalendarPanelHandle calendarPanelHandle; + + @Before + public void setUp() { + calendarPanel = new CalendarPanel(); + guiRobot.interact(() -> calendarPanel = new CalendarPanel()); + uiPartRule.setUiPart(calendarPanel); + + calendarPanelHandle = new CalendarPanelHandle(calendarPanel.getRoot()); + } + + @Test + public void display() { + // calendar view page is not null + assertNotNull(calendarPanel.getRoot()); + + // default view page of calendar + assertEquals(calendarPanel.getRoot().getSelectedPage(), calendarPanelHandle.getDefaultCalendarViewPage()); + + // view page changes to week + CalendarPanel.changeViewPage('w'); + assertEquals(calendarPanelHandle.getCurrentCalendarViewPage(), calendarPanelHandle.getWeekViewPage()); + + // view page changes to month + CalendarPanel.changeViewPage('m'); + assertEquals(calendarPanelHandle.getCurrentCalendarViewPage(), calendarPanelHandle.getMonthViewPage()); + + // view page changes to year + CalendarPanel.changeViewPage('y'); + assertEquals(calendarPanelHandle.getCurrentCalendarViewPage(), calendarPanelHandle.getYearViewPage()); + + // view page changes to day (default) + CalendarPanel.changeViewPage('d'); + assertEquals(calendarPanelHandle.getCurrentCalendarViewPage(), + calendarPanelHandle.getDefaultCalendarViewPage()); + } +} +``` +###### \java\systemtests\AddressBookSystemTest.java +``` java + /** + * Asserts that the starting calendar view of the application is correct. + */ + private void assertStartingCalendarViewPageIsCorrect() { + assertEquals(getCalendarPanel().getDefaultCalendarViewPage(), getCalendarPanel().getCurrentCalendarViewPage()); + } +``` +###### \java\systemtests\AddTuteeCommandSystemTest.java +``` java +public class AddTuteeCommandSystemTest extends AddressBookSystemTest { + + @Test + public void addtutee() throws Exception { + Model model = getModel(); + + /* ------------------------ Perform add operations on the shown unfiltered list ----------------------------- */ + + /* Case: add a tutee without tags to a non-empty address book, command with leading spaces and trailing spaces + * -> added + */ + Tutee toAdd = AMYTUTEE; + String command = " " + AddTuteeCommand.COMMAND_WORD + " " + NAME_DESC_AMY + " " + PHONE_DESC_AMY + " " + + EMAIL_DESC_AMY + " " + ADDRESS_DESC_AMY + " " + SUBJECT_DESC_AMY + GRADE_DESC_AMY + + EDUCATION_LEVEL_DESC_AMY + SCHOOL_DESC_AMY + TAG_DESC_FRIEND + " "; + assertCommandSuccess(command, toAdd); + + /* Case: undo adding Amy to the list -> Amy deleted */ + command = UndoCommand.COMMAND_WORD; + String expectedResultMessage = UndoCommand.MESSAGE_SUCCESS; + assertCommandSuccess(command, model, expectedResultMessage); + + /* Case: redo adding Amy to the list -> Amy added again */ + command = RedoCommand.COMMAND_WORD; + model.addPerson(toAdd); + expectedResultMessage = RedoCommand.MESSAGE_SUCCESS; + assertCommandSuccess(command, model, expectedResultMessage); + + /* Case: add a tutee with all fields same as another tutee in the address book except name -> added */ + toAdd = new TuteeBuilder().withName(VALID_NAME_BOB).withPhone(VALID_PHONE_AMY).withEmail(VALID_EMAIL_AMY) + .withAddress(VALID_ADDRESS_AMY).withSubject(VALID_SUBJECT_AMY).withGrade(VALID_GRADE_AMY) + .withEducationLevel(VALID_EDUCATION_LEVEL_AMY).withSchool(VALID_SCHOOL_AMY).withTags(VALID_TAG_FRIEND) + .build(); + command = AddTuteeCommand.COMMAND_WORD + NAME_DESC_BOB + PHONE_DESC_AMY + EMAIL_DESC_AMY + ADDRESS_DESC_AMY + + SUBJECT_DESC_AMY + GRADE_DESC_AMY + EDUCATION_LEVEL_DESC_AMY + SCHOOL_DESC_AMY + TAG_DESC_FRIEND; + assertCommandSuccess(command, toAdd); + + /* Case: add a tutee with all fields same as another tutee in the address book except phone -> added */ + toAdd = new TuteeBuilder().withName(VALID_NAME_AMY).withPhone(VALID_PHONE_BOB).withEmail(VALID_EMAIL_AMY) + .withAddress(VALID_ADDRESS_AMY).withTags(VALID_TAG_FRIEND).withSubject(VALID_SUBJECT_AMY) + .withGrade(VALID_GRADE_AMY).withEducationLevel(VALID_EDUCATION_LEVEL_AMY).withSchool(VALID_SCHOOL_AMY) + .withTags(VALID_TAG_FRIEND).build(); + command = AddTuteeCommand.COMMAND_WORD + NAME_DESC_AMY + PHONE_DESC_BOB + EMAIL_DESC_AMY + ADDRESS_DESC_AMY + + SUBJECT_DESC_AMY + GRADE_DESC_AMY + EDUCATION_LEVEL_DESC_AMY + SCHOOL_DESC_AMY + TAG_DESC_FRIEND; + assertCommandSuccess(command, toAdd); + + /* Case: add a tutee with all fields same as another tutee in the address book except email -> added */ + toAdd = new TuteeBuilder().withName(VALID_NAME_AMY).withPhone(VALID_PHONE_AMY).withEmail(VALID_EMAIL_BOB) + .withAddress(VALID_ADDRESS_AMY).withSubject(VALID_SUBJECT_AMY).withGrade(VALID_GRADE_AMY) + .withEducationLevel(VALID_EDUCATION_LEVEL_AMY).withSchool(VALID_SCHOOL_AMY).withTags(VALID_TAG_FRIEND) + .withTags(VALID_TAG_FRIEND).build(); + command = AddTuteeCommand.COMMAND_WORD + NAME_DESC_AMY + PHONE_DESC_AMY + EMAIL_DESC_BOB + ADDRESS_DESC_AMY + + SUBJECT_DESC_AMY + GRADE_DESC_AMY + EDUCATION_LEVEL_DESC_AMY + SCHOOL_DESC_AMY + TAG_DESC_FRIEND; + assertCommandSuccess(command, toAdd); + + /* Case: add a tutee with all fields same as another tutee in the address book except address -> added */ + toAdd = new TuteeBuilder().withName(VALID_NAME_AMY).withPhone(VALID_PHONE_AMY).withEmail(VALID_EMAIL_AMY) + .withAddress(VALID_ADDRESS_BOB).withSubject(VALID_SUBJECT_AMY).withGrade(VALID_GRADE_AMY) + .withEducationLevel(VALID_EDUCATION_LEVEL_AMY).withSchool(VALID_SCHOOL_AMY).withTags(VALID_TAG_FRIEND) + .withTags(VALID_TAG_FRIEND).build(); + command = AddTuteeCommand.COMMAND_WORD + NAME_DESC_AMY + PHONE_DESC_AMY + EMAIL_DESC_AMY + ADDRESS_DESC_BOB + + SUBJECT_DESC_AMY + GRADE_DESC_AMY + EDUCATION_LEVEL_DESC_AMY + SCHOOL_DESC_AMY + TAG_DESC_FRIEND; + assertCommandSuccess(command, toAdd); + + /* Case: add to empty address book -> added */ + deleteAllPersons(); + assertCommandSuccess(ALICETUTEE); + + /* Case: add a tutee with tags, command with parameters in random order -> added */ + toAdd = BOBTUTEE; + command = AddTuteeCommand.COMMAND_WORD + TAG_DESC_FRIEND + PHONE_DESC_BOB + ADDRESS_DESC_BOB + NAME_DESC_BOB + + TAG_DESC_HUSBAND + EMAIL_DESC_BOB + GRADE_DESC_BOB + SUBJECT_DESC_BOB + EDUCATION_LEVEL_DESC_BOB + + SCHOOL_DESC_BOB; + assertCommandSuccess(command, toAdd); + + /* Case: add a tutee, missing tags -> added */ + assertCommandSuccess(HOONTUTEE); + + /* -------------------------- Perform add operation on the shown filtered list ------------------------------ */ + + /* Case: filters the contact list before adding -> added */ + showPersonsWithName(KEYWORD_MATCHING_MEIER); + assertCommandSuccess(IDATUTEE); + + /* ----------------------------------- Perform invalid add operations --------------------------------------- */ + + /* Case: add a duplicate tutee -> rejected */ + command = TuteeUtil.getAddTuteeCommand(HOONTUTEE); + assertCommandFailure(command, AddTuteeCommand.MESSAGE_DUPLICATE_PERSON); + + /* Case: add a duplicate tutee except with different tags -> rejected */ + // "friends" is an existing tag used in the default model, see TypicalPersons#ALICE + // This test will fail if a new tag that is not in the model is used, see the bug documented in + // AddressBook#addPerson(Person) + command = TuteeUtil.getAddTuteeCommand(HOONTUTEE) + " " + PREFIX_TAG.getPrefix() + "friends"; + assertCommandFailure(command, AddTuteeCommand.MESSAGE_DUPLICATE_PERSON); + + /* Case: missing name -> rejected */ + command = AddTuteeCommand.COMMAND_WORD + PHONE_DESC_AMY + EMAIL_DESC_AMY + ADDRESS_DESC_AMY + + SUBJECT_DESC_AMY + GRADE_DESC_AMY + EDUCATION_LEVEL_DESC_AMY + SCHOOL_DESC_AMY; + assertCommandFailure(command, String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddTuteeCommand.MESSAGE_USAGE)); + + /* Case: missing phone -> rejected */ + command = AddTuteeCommand.COMMAND_WORD + NAME_DESC_AMY + EMAIL_DESC_AMY + ADDRESS_DESC_AMY + + SUBJECT_DESC_AMY + GRADE_DESC_AMY + EDUCATION_LEVEL_DESC_AMY + SCHOOL_DESC_AMY; + assertCommandFailure(command, String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddTuteeCommand.MESSAGE_USAGE)); + + /* Case: missing email -> rejected */ + command = AddTuteeCommand.COMMAND_WORD + NAME_DESC_AMY + PHONE_DESC_AMY + ADDRESS_DESC_AMY + + SUBJECT_DESC_AMY + GRADE_DESC_AMY + EDUCATION_LEVEL_DESC_AMY + SCHOOL_DESC_AMY; + assertCommandFailure(command, String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddTuteeCommand.MESSAGE_USAGE)); + + /* Case: missing address -> rejected */ + command = AddTuteeCommand.COMMAND_WORD + NAME_DESC_AMY + PHONE_DESC_AMY + EMAIL_DESC_AMY + + SUBJECT_DESC_AMY + GRADE_DESC_AMY + EDUCATION_LEVEL_DESC_AMY + SCHOOL_DESC_AMY; + assertCommandFailure(command, String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddTuteeCommand.MESSAGE_USAGE)); + + /* Case: missing subject -> rejected */ + command = AddTuteeCommand.COMMAND_WORD + NAME_DESC_AMY + PHONE_DESC_AMY + EMAIL_DESC_AMY + + ADDRESS_DESC_AMY + GRADE_DESC_AMY + EDUCATION_LEVEL_DESC_AMY + SCHOOL_DESC_AMY; + assertCommandFailure(command, String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddTuteeCommand.MESSAGE_USAGE)); + + /* Case: missing grade -> rejected */ + command = AddTuteeCommand.COMMAND_WORD + NAME_DESC_AMY + PHONE_DESC_AMY + EMAIL_DESC_AMY + + ADDRESS_DESC_AMY + GRADE_DESC_AMY + EDUCATION_LEVEL_DESC_AMY + SCHOOL_DESC_AMY; + assertCommandFailure(command, String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddTuteeCommand.MESSAGE_USAGE)); + + /* Case: missing education level -> rejected */ + command = AddTuteeCommand.COMMAND_WORD + NAME_DESC_AMY + PHONE_DESC_AMY + EMAIL_DESC_AMY + + ADDRESS_DESC_AMY + SUBJECT_DESC_AMY + GRADE_DESC_AMY + SCHOOL_DESC_AMY; + assertCommandFailure(command, String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddTuteeCommand.MESSAGE_USAGE)); + + /* Case: missing school -> rejected */ + command = AddTuteeCommand.COMMAND_WORD + NAME_DESC_AMY + PHONE_DESC_AMY + EMAIL_DESC_AMY + + ADDRESS_DESC_AMY + SUBJECT_DESC_AMY + GRADE_DESC_AMY + EDUCATION_LEVEL_DESC_AMY; + assertCommandFailure(command, String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddTuteeCommand.MESSAGE_USAGE)); + + /* Case: invalid keyword -> rejected */ + command = "addtutees " + TuteeUtil.getTuteeDetails(toAdd); + assertCommandFailure(command, Messages.MESSAGE_UNKNOWN_COMMAND); + + /* Case: invalid name -> rejected */ + command = AddTuteeCommand.COMMAND_WORD + INVALID_NAME_DESC + PHONE_DESC_AMY + EMAIL_DESC_AMY + ADDRESS_DESC_AMY + + SUBJECT_DESC_AMY + GRADE_DESC_AMY + EDUCATION_LEVEL_DESC_AMY + SCHOOL_DESC_AMY; + assertCommandFailure(command, Name.MESSAGE_NAME_CONSTRAINTS); + + /* Case: invalid phone -> rejected */ + command = AddTuteeCommand.COMMAND_WORD + NAME_DESC_AMY + INVALID_PHONE_DESC + EMAIL_DESC_AMY + ADDRESS_DESC_AMY + + SUBJECT_DESC_AMY + GRADE_DESC_AMY + EDUCATION_LEVEL_DESC_AMY + SCHOOL_DESC_AMY; + assertCommandFailure(command, Phone.MESSAGE_PHONE_CONSTRAINTS); + + /* Case: invalid email -> rejected */ + command = AddTuteeCommand.COMMAND_WORD + NAME_DESC_AMY + PHONE_DESC_AMY + INVALID_EMAIL_DESC + ADDRESS_DESC_AMY + + SUBJECT_DESC_AMY + GRADE_DESC_AMY + EDUCATION_LEVEL_DESC_AMY + SCHOOL_DESC_AMY; + assertCommandFailure(command, Email.MESSAGE_EMAIL_CONSTRAINTS); + + /* Case: invalid address -> rejected */ + command = AddTuteeCommand.COMMAND_WORD + NAME_DESC_AMY + PHONE_DESC_AMY + EMAIL_DESC_AMY + INVALID_ADDRESS_DESC + + SUBJECT_DESC_AMY + GRADE_DESC_AMY + EDUCATION_LEVEL_DESC_AMY + SCHOOL_DESC_AMY; + assertCommandFailure(command, Address.MESSAGE_ADDRESS_CONSTRAINTS); + + /* Case: invalid tag -> rejected */ + command = AddTuteeCommand.COMMAND_WORD + NAME_DESC_AMY + PHONE_DESC_AMY + EMAIL_DESC_AMY + ADDRESS_DESC_AMY + + SUBJECT_DESC_AMY + GRADE_DESC_AMY + EDUCATION_LEVEL_DESC_AMY + SCHOOL_DESC_AMY + INVALID_TAG_DESC; + assertCommandFailure(command, Tag.MESSAGE_TAG_CONSTRAINTS); + + /* Case: invalid subject -> rejected */ + command = AddTuteeCommand.COMMAND_WORD + NAME_DESC_AMY + PHONE_DESC_AMY + EMAIL_DESC_AMY + ADDRESS_DESC_AMY + + INVALID_SUBJECT_DESC + GRADE_DESC_AMY + EDUCATION_LEVEL_DESC_AMY + SCHOOL_DESC_AMY + INVALID_TAG_DESC; + assertCommandFailure(command, Subject.MESSAGE_SUBJECT_CONSTRAINTS); + + /* Case: invalid grade -> rejected */ + command = AddTuteeCommand.COMMAND_WORD + NAME_DESC_AMY + PHONE_DESC_AMY + EMAIL_DESC_AMY + ADDRESS_DESC_AMY + + SUBJECT_DESC_AMY + INVALID_GRADE_DESC + EDUCATION_LEVEL_DESC_AMY + SCHOOL_DESC_AMY + INVALID_TAG_DESC; + assertCommandFailure(command, Grade.MESSAGE_GRADE_CONSTRAINTS); + + /* Case: invalid education level -> rejected */ + command = AddTuteeCommand.COMMAND_WORD + NAME_DESC_AMY + PHONE_DESC_AMY + EMAIL_DESC_AMY + ADDRESS_DESC_AMY + + SUBJECT_DESC_AMY + GRADE_DESC_AMY + INVALID_EDUCATION_LEVEL + SCHOOL_DESC_AMY + INVALID_TAG_DESC; + assertCommandFailure(command, EducationLevel.MESSAGE_EDUCATION_LEVEL_CONSTRAINTS); + + /* Case: invalid school -> rejected */ + command = AddTuteeCommand.COMMAND_WORD + NAME_DESC_AMY + PHONE_DESC_AMY + EMAIL_DESC_AMY + ADDRESS_DESC_AMY + + SUBJECT_DESC_AMY + GRADE_DESC_AMY + EDUCATION_LEVEL_DESC_AMY + INVALID_SCHOOL + INVALID_TAG_DESC; + assertCommandFailure(command, School.MESSAGE_SCHOOL_CONSTRAINTS); + } + /** + * Executes the {@code AddTuteeCommand} that adds {@code toAdd} to the model and asserts that the,
+ * 1. Command box displays an empty string.
+ * 2. Command box has the default style class.
+ * 3. Result display box displays the success message of executing {@code AddTuteeCommand} with the details of + * {@code toAdd}.
+ * 4. {@code Model}, {@code Storage} and {@code PersonListPanel} equal to the corresponding components in + * the current model added with {@code toAdd}.
+ * 5. Browser url and selected card remain unchanged.
+ * 6. Status bar's sync status changes.
+ * Verifications 1, 3 and 4 are performed by + * {@code AddressBookSystemTest#assertApplicationDisplaysExpected(String, String, Model)}.
+ * @see AddressBookSystemTest#assertApplicationDisplaysExpected(String, String, Model) + */ + private void assertCommandSuccess(Tutee toAdd) { + assertCommandSuccess(TuteeUtil.getAddTuteeCommand(toAdd), toAdd); + } + + /** + * Performs the same verification as {@code assertCommandSuccess(Tutee)}. Executes {@code command} + * instead. + * @see AddTuteeCommandSystemTest#assertCommandSuccess(Tutee) + */ + private void assertCommandSuccess(String command, Tutee toAdd) { + Model expectedModel = getModel(); + try { + expectedModel.addPerson(toAdd); + } catch (DuplicatePersonException dpe) { + throw new IllegalArgumentException("toAdd already exists in the model."); + } + String expectedResultMessage = String.format(AddTuteeCommand.MESSAGE_SUCCESS, toAdd); + + assertCommandSuccess(command, expectedModel, expectedResultMessage); + } + + /** + * Performs the same verification as {@code assertCommandSuccess(String, Tutee)} except asserts that + * the,
+ * 1. Result display box displays {@code expectedResultMessage}.
+ * 2. {@code Model}, {@code Storage} and {@code PersonListPanel} equal to the corresponding components in + * {@code expectedModel}.
+ * @see AddTuteeCommandSystemTest#assertCommandSuccess(String, Tutee) + */ + private void assertCommandSuccess(String command, Model expectedModel, String expectedResultMessage) { + executeCommand(command); + assertApplicationDisplaysExpected("", expectedResultMessage, expectedModel); + assertSelectedCardUnchanged(); + assertCommandBoxShowsDefaultStyle(); + assertStatusBarUnchangedExceptSyncStatus(); + } + + /** + * Executes {@code command} and asserts that the,
+ * 1. Command box displays {@code command}.
+ * 2. Command box has the error style class.
+ * 3. Result display box displays {@code expectedResultMessage}.
+ * 4. {@code Model}, {@code Storage} and {@code PersonListPanel} remain unchanged.
+ * 5. Browser url, selected card and status bar remain unchanged.
+ * Verifications 1, 3 and 4 are performed by + * {@code AddressBookSystemTest#assertApplicationDisplaysExpected(String, String, Model)}.
+ * @see AddressBookSystemTest#assertApplicationDisplaysExpected(String, String, Model) + */ + private void assertCommandFailure(String command, String expectedResultMessage) { + Model expectedModel = getModel(); + + executeCommand(command); + assertApplicationDisplaysExpected(command, expectedResultMessage, expectedModel); + assertSelectedCardUnchanged(); + assertCommandBoxShowsErrorStyle(); + assertStatusBarUnchanged(); + } + +} +``` diff --git a/collated/test/Raymond511.md b/collated/test/Raymond511.md new file mode 100644 index 000000000000..11ed3990a19e --- /dev/null +++ b/collated/test/Raymond511.md @@ -0,0 +1,1620 @@ +# ChoChihTun +###### \java\guitests\guihandles\CalendarPanelHandle.java +``` java +/** + * A handler for the {@code CalendarPanel} of the UI + */ +public class CalendarPanelHandle extends NodeHandle { + + public static final String CALENDAR_PANEL_ID = "#calendarPlaceholder"; + + private CalendarPanel calendarPanel; + + public CalendarPanelHandle(Node calendarPanelNode) { + super(calendarPanelNode); + calendarPanel = new CalendarPanel(); + } + + public PageBase getDefaultCalendarViewPage() { + return calendarPanel.getRoot().getDayPage(); + } + + public PageBase getWeekViewPage() { + return calendarPanel.getRoot().getWeekPage(); + } + + public PageBase getMonthViewPage() { + return calendarPanel.getRoot().getMonthPage(); + } + + public PageBase getYearViewPage() { + return calendarPanel.getRoot().getYearPage(); + } + + public PageBase getCurrentCalendarViewPage() { + return calendarPanel.getRoot().getSelectedPage(); + } +} +``` +###### \java\seedu\address\logic\commands\AddTuteeCommandTest.java +``` java +public class AddTuteeCommandTest { + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @Test + public void constructor_nullPerson_throwsNullPointerException() { + thrown.expect(NullPointerException.class); + new AddTuteeCommand(null); + } + + @Test + public void execute_personAcceptedByModel_addSuccessful() throws Exception { + ModelStubAcceptingPersonAdded modelStub = new ModelStubAcceptingPersonAdded(); + Tutee validTutee = new TuteeBuilder().build(); + + CommandResult commandResult = getAddTuteeCommandForTutee(validTutee, modelStub).execute(); + + assertEquals(String.format(AddTuteeCommand.MESSAGE_SUCCESS, validTutee), commandResult.feedbackToUser); + assertEquals(Arrays.asList(validTutee), modelStub.personsAdded); + } + + @Test + public void execute_duplicatePerson_throwsCommandException() throws Exception { + ModelStub modelStub = new ModelStubThrowingDuplicatePersonException(); + Tutee validTutee = new TuteeBuilder().build(); + + thrown.expect(CommandException.class); + thrown.expectMessage(AddTuteeCommand.MESSAGE_DUPLICATE_PERSON); + + getAddTuteeCommandForTutee(validTutee, modelStub).execute(); + } + + @Test + public void equals() { + Tutee alice = new TuteeBuilder().withName("Alice").build(); + Tutee bob = new TuteeBuilder().withName("Bob").build(); + AddTuteeCommand addAliceCommand = new AddTuteeCommand(alice); + AddTuteeCommand addBobCommand = new AddTuteeCommand(bob); + + // same object -> returns true + assertTrue(addAliceCommand.equals(addAliceCommand)); + + // same values -> returns true + AddTuteeCommand addAliceCommandCopy = new AddTuteeCommand(alice); + assertTrue(addAliceCommand.equals(addAliceCommandCopy)); + + // different types -> returns false + assertFalse(addAliceCommand.equals(1)); + + // null -> returns false + assertFalse(addAliceCommand.equals(null)); + + // different person -> returns false + assertFalse(addAliceCommand.equals(addBobCommand)); + } + + /** + * Generates a new AddTuteeCommand with the details of the given tutee. + */ + private AddTuteeCommand getAddTuteeCommandForTutee(Tutee tutee, Model model) { + AddTuteeCommand command = new AddTuteeCommand(tutee); + command.setData(model, new CommandHistory(), new UndoRedoStack()); + return command; + } + + /** + * A Model stub that always throw a DuplicatePersonException when trying to add a person. + */ + private class ModelStubThrowingDuplicatePersonException extends ModelStub { + @Override + public void addPerson(Person person) throws DuplicatePersonException { + throw new DuplicatePersonException(); + } + + @Override + public ReadOnlyAddressBook getAddressBook() { + return new AddressBook(); + } + } + + /** + * A Model stub that always accept the person being added. + */ + private class ModelStubAcceptingPersonAdded extends ModelStub { + final ArrayList personsAdded = new ArrayList<>(); + + @Override + public void addPerson(Person person) throws DuplicatePersonException { + requireNonNull(person); + personsAdded.add(person); + } + + @Override + public ReadOnlyAddressBook getAddressBook() { + return new AddressBook(); + } + } + +} +``` +###### \java\seedu\address\logic\parser\AddPersonalTaskCommandParserTest.java +``` java +public class AddPersonalTaskCommandParserTest { + private AddPersonalTaskCommandParser parser = new AddPersonalTaskCommandParser(); + + @Test + public void parse_invalidArgs_throwsParseException() { + // Invalid format + assertParseFailure(parser, "a", + String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddPersonalTaskCommand.MESSAGE_USAGE)); + assertParseFailure(parser, "1 11/01/2018 11:11 1h30m Outing", + String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddPersonalTaskCommand.MESSAGE_USAGE)); + assertParseFailure(parser, "11:11 1h30m Outing with friends", + String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddPersonalTaskCommand.MESSAGE_USAGE)); + assertParseFailure(parser, "11/01/2018 1h30m Outing with friends", + String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddPersonalTaskCommand.MESSAGE_USAGE)); + assertParseFailure(parser, "11/01/2018 11:11 Outing with friends", + String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddPersonalTaskCommand.MESSAGE_USAGE)); + assertParseFailure(parser, "11/01/2018 11:11 1h Outing with friends", + String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddPersonalTaskCommand.MESSAGE_USAGE)); + assertParseFailure(parser, "11/01/2018 11:11 30m Outing with friends", + String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddPersonalTaskCommand.MESSAGE_USAGE)); + assertParseFailure(parser, "11:11 32/01/2018 1h30m Outing with friends", + String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddPersonalTaskCommand.MESSAGE_USAGE)); + assertParseFailure(parser, "aa/01/2018 11:11 1h30m Outing with friends", + String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddPersonalTaskCommand.MESSAGE_USAGE)); + assertParseFailure(parser, "32/01/2018 11:aa 1h30m Outing with friends", + String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddPersonalTaskCommand.MESSAGE_USAGE)); + assertParseFailure(parser, "32/01/2018 11:11 1haam Outing with friends", + String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddPersonalTaskCommand.MESSAGE_USAGE)); + + // Invalid date + assertParseFailure(parser, "29/02/2018 11:11 1h30m Outing with friends", + MESSAGE_INVALID_DATE_TIME + "\n" + AddPersonalTaskCommand.MESSAGE_USAGE); + assertParseFailure(parser, "32/01/2018 11:11 1h30m Outing with friends", + MESSAGE_INVALID_DATE_TIME + "\n" + AddPersonalTaskCommand.MESSAGE_USAGE); + assertParseFailure(parser, "31/04/2018 11:11 1h30m Outing with friends", + MESSAGE_INVALID_DATE_TIME + "\n" + AddPersonalTaskCommand.MESSAGE_USAGE); + + // Invalid time + assertParseFailure(parser, "11/01/2018 24:00 1h30m Outing with friends", + MESSAGE_INVALID_DATE_TIME + "\n" + AddPersonalTaskCommand.MESSAGE_USAGE); + assertParseFailure(parser, "11/01/2018 11:60 1h30m Outing with friends", + MESSAGE_INVALID_DATE_TIME + "\n" + AddPersonalTaskCommand.MESSAGE_USAGE); + + // Invalid duration + assertParseFailure(parser, "11/01/2018 11:11 1h60m Outing with friends", + MESSAGE_INVALID_DURATION + "\n" + AddPersonalTaskCommand.MESSAGE_USAGE); + assertParseFailure(parser, "11/01/2018 11:11 24h0m Outing with friends", + MESSAGE_INVALID_DURATION + "\n" + AddPersonalTaskCommand.MESSAGE_USAGE); + } + + @Test + public void parse_validArgs_success() { + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd/MM/uuuu HH:mm") + .withResolverStyle(ResolverStyle.STRICT); + + // With description + LocalDateTime taskDateTime = LocalDateTime.parse(VALID_DATE_TIME, formatter); + PersonalTask personalTask = new PersonalTask(taskDateTime, VALID_DURATION, VALID_TASK_DESC); + assertParseSuccess(parser, VALID_TASK_WITH_DESC, + new AddPersonalTaskCommand(personalTask)); + + // Without description + personalTask = new PersonalTask(taskDateTime, VALID_DURATION, VALID_EMPTY_TASK_DESC); + assertParseSuccess(parser, VALID_TASK_WITHOUT_DESC, + new AddPersonalTaskCommand(personalTask)); + + // Valid date + personalTask = new PersonalTask(LocalDateTime.parse("29/02/2016 11:20", formatter), + VALID_DURATION, VALID_EMPTY_TASK_DESC); + assertParseSuccess(parser, "29/02/2016 11:20 1h11m", + new AddPersonalTaskCommand(personalTask)); + + personalTask = new PersonalTask(LocalDateTime.parse("30/04/2016 11:20", formatter), + VALID_DURATION, VALID_EMPTY_TASK_DESC); + assertParseSuccess(parser, "30/04/2016 11:20 " + VALID_DURATION, + new AddPersonalTaskCommand(personalTask)); + + personalTask = new PersonalTask(LocalDateTime.parse("31/01/2016 11:20", formatter), + VALID_DURATION, VALID_EMPTY_TASK_DESC); + assertParseSuccess(parser, "31/01/2016 11:20 " + VALID_DURATION, + new AddPersonalTaskCommand(personalTask)); + + // Valid Time + personalTask = new PersonalTask(LocalDateTime.parse("11/01/2018 00:00", formatter), + VALID_DURATION, VALID_EMPTY_TASK_DESC); + assertParseSuccess(parser, "11/01/2018 00:00 " + VALID_DURATION, + new AddPersonalTaskCommand(personalTask)); + } +} +``` +###### \java\seedu\address\logic\parser\AddTuitionTaskCommandParserTest.java +``` java +public class AddTuitionTaskCommandParserTest { + private AddTuitionTaskCommandParser parser = new AddTuitionTaskCommandParser(); + + @Test + public void parse_invalidArgs_throwsParseException() { + // Invalid format + assertParseFailure(parser, "1", + String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddTuitionTaskCommand.MESSAGE_USAGE)); + assertParseFailure(parser, "11/01/2018 11:11 1h30m tuition homework", + String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddTuitionTaskCommand.MESSAGE_USAGE)); + assertParseFailure(parser, "1 11:11 1h30m tuition homework", + String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddTuitionTaskCommand.MESSAGE_USAGE)); + assertParseFailure(parser, "1 11/01/2018 1h30m tuition homework", + String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddTuitionTaskCommand.MESSAGE_USAGE)); + assertParseFailure(parser, "1 11/01/2018 11:11 tuition homework", + String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddTuitionTaskCommand.MESSAGE_USAGE)); + assertParseFailure(parser, "1 11/01/2018 11:11 1h tuition homework", + String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddTuitionTaskCommand.MESSAGE_USAGE)); + assertParseFailure(parser, "1 11/01/2018 11:11 30m tuition homework", + String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddTuitionTaskCommand.MESSAGE_USAGE)); + assertParseFailure(parser, "1 11:11 32/01/2018 1h30m tuition homework", + String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddTuitionTaskCommand.MESSAGE_USAGE)); + assertParseFailure(parser, "aaa 32/01/2018 11:11 1h30m tuition homework", + String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddTuitionTaskCommand.MESSAGE_USAGE)); + assertParseFailure(parser, "1 aa/01/2018 11:11 1h30m tuition homework", + String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddTuitionTaskCommand.MESSAGE_USAGE)); + assertParseFailure(parser, "1 32/01/2018 11:aa 1h30m tuition homework", + String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddTuitionTaskCommand.MESSAGE_USAGE)); + assertParseFailure(parser, "1 32/01/2018 11:11 1haam tuition homework", + String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddTuitionTaskCommand.MESSAGE_USAGE)); + + // Invalid date + assertParseFailure(parser, "1 29/02/2018 11:11 1h30m tuition homework", + MESSAGE_INVALID_DATE_TIME + "\n" + AddTuitionTaskCommand.MESSAGE_USAGE); + assertParseFailure(parser, "1 31/04/2018 11:11 1h30m tuition homework", + MESSAGE_INVALID_DATE_TIME + "\n" + AddTuitionTaskCommand.MESSAGE_USAGE); + assertParseFailure(parser, "1 32/01/2018 11:11 1h30m tuition homework", + MESSAGE_INVALID_DATE_TIME + "\n" + AddTuitionTaskCommand.MESSAGE_USAGE); + + // Invalid time + assertParseFailure(parser, "1 11/01/2018 24:00 1h30m tuition homework", + MESSAGE_INVALID_DATE_TIME + "\n" + AddTuitionTaskCommand.MESSAGE_USAGE); + assertParseFailure(parser, "1 11/01/2018 11:60 1h30m tuition homework", + MESSAGE_INVALID_DATE_TIME + "\n" + AddTuitionTaskCommand.MESSAGE_USAGE); + + // Invalid duration + assertParseFailure(parser, "1 11/01/2018 11:11 1h60m tuition homework", + MESSAGE_INVALID_DURATION + "\n" + AddTuitionTaskCommand.MESSAGE_USAGE); + assertParseFailure(parser, "1 11/01/2018 11:11 24h0m tuition homework", + MESSAGE_INVALID_DURATION + "\n" + AddTuitionTaskCommand.MESSAGE_USAGE); + } + + @Test + public void parse_validArgs_success() { + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd/MM/uuuu HH:mm") + .withResolverStyle(ResolverStyle.STRICT); + + LocalDateTime taskDateTime = LocalDateTime.parse(VALID_DATE_TIME, formatter); + + // With description + assertParseSuccess(parser, "1 " + VALID_TASK_WITH_DESC, + new AddTuitionTaskCommand(INDEX_FIRST_PERSON, taskDateTime, VALID_DURATION, VALID_TASK_DESC)); + + // Without description + assertParseSuccess(parser, "1 " + VALID_TASK_WITHOUT_DESC, + new AddTuitionTaskCommand(INDEX_FIRST_PERSON, taskDateTime, VALID_DURATION, VALID_EMPTY_TASK_DESC)); + + // Valid date + + taskDateTime = LocalDateTime.parse("28/02/2018 11:20", formatter); + assertParseSuccess(parser, "1 28/02/2018 11:20 " + VALID_DURATION, + new AddTuitionTaskCommand(INDEX_FIRST_PERSON, taskDateTime, VALID_DURATION, VALID_EMPTY_TASK_DESC)); + + taskDateTime = LocalDateTime.parse("29/02/2016 11:20", formatter); + assertParseSuccess(parser, "1 29/02/2016 11:20 " + VALID_DURATION, + new AddTuitionTaskCommand(INDEX_FIRST_PERSON, taskDateTime, VALID_DURATION, VALID_EMPTY_TASK_DESC)); + + taskDateTime = LocalDateTime.parse("30/04/2016 11:20", formatter); + assertParseSuccess(parser, "1 30/04/2016 11:20 " + VALID_DURATION, + new AddTuitionTaskCommand(INDEX_FIRST_PERSON, taskDateTime, VALID_DURATION, VALID_EMPTY_TASK_DESC)); + + taskDateTime = LocalDateTime.parse("31/01/2016 11:20", formatter); + assertParseSuccess(parser, "1 31/01/2016 11:20 " + VALID_DURATION, + new AddTuitionTaskCommand(INDEX_FIRST_PERSON, taskDateTime, VALID_DURATION, VALID_EMPTY_TASK_DESC)); + + // Valid Time + taskDateTime = LocalDateTime.parse("11/01/2018 00:00", formatter); + assertParseSuccess(parser, "1 11/01/2018 00:00 " + VALID_DURATION, + new AddTuitionTaskCommand(INDEX_FIRST_PERSON, taskDateTime, VALID_DURATION, VALID_EMPTY_TASK_DESC)); + } + +} +``` +###### \java\seedu\address\logic\parser\AddTuteeCommandParserTest.java +``` java +public class AddTuteeCommandParserTest { + private AddTuteeCommandParser parser = new AddTuteeCommandParser(); + + @Test + public void parse_allFieldsPresent_success() { + Tutee expectedTutee = new TuteeBuilder().withName(VALID_NAME_BOB).withPhone(VALID_PHONE_BOB) + .withEmail(VALID_EMAIL_BOB).withAddress(VALID_ADDRESS_BOB).withSubject(VALID_SUBJECT_BOB) + .withGrade(VALID_GRADE_BOB).withEducationLevel(VALID_EDUCATION_LEVEL_BOB).withSchool(VALID_SCHOOL_BOB) + .withTags(VALID_TAG_FRIEND).build(); + + // whitespace only preamble + assertParseSuccess(parser, PREAMBLE_WHITESPACE + NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + + ADDRESS_DESC_BOB + SUBJECT_DESC_BOB + GRADE_DESC_BOB + EDUCATION_LEVEL_DESC_BOB + + SCHOOL_DESC_BOB + TAG_DESC_FRIEND, new AddTuteeCommand(expectedTutee)); + + // multiple names - last name accepted + assertParseSuccess(parser, NAME_DESC_AMY + NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + + ADDRESS_DESC_BOB + SUBJECT_DESC_BOB + GRADE_DESC_BOB + EDUCATION_LEVEL_DESC_BOB + + SCHOOL_DESC_BOB + TAG_DESC_FRIEND + TAG_DESC_FRIEND, new AddTuteeCommand(expectedTutee)); + + // multiple phones - last phone accepted + assertParseSuccess(parser, NAME_DESC_BOB + PHONE_DESC_AMY + PHONE_DESC_BOB + EMAIL_DESC_BOB + + ADDRESS_DESC_BOB + SUBJECT_DESC_BOB + GRADE_DESC_BOB + EDUCATION_LEVEL_DESC_BOB + + SCHOOL_DESC_BOB + TAG_DESC_FRIEND, new AddTuteeCommand(expectedTutee)); + + // multiple emails - last email accepted + assertParseSuccess(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_AMY + EMAIL_DESC_BOB + + ADDRESS_DESC_BOB + SUBJECT_DESC_BOB + GRADE_DESC_BOB + EDUCATION_LEVEL_DESC_BOB + + SCHOOL_DESC_BOB + TAG_DESC_FRIEND, new AddTuteeCommand(expectedTutee)); + + // multiple addresses - last address accepted + assertParseSuccess(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + ADDRESS_DESC_AMY + + ADDRESS_DESC_BOB + SUBJECT_DESC_BOB + GRADE_DESC_BOB + EDUCATION_LEVEL_DESC_BOB + + SCHOOL_DESC_BOB + TAG_DESC_FRIEND, new AddTuteeCommand(expectedTutee)); + + // multiple subjects - last subject accepted + assertParseSuccess(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + ADDRESS_DESC_BOB + + SUBJECT_DESC_AMY + SUBJECT_DESC_BOB + GRADE_DESC_BOB + EDUCATION_LEVEL_DESC_BOB + + SCHOOL_DESC_BOB + TAG_DESC_FRIEND, new AddTuteeCommand(expectedTutee)); + + // multiple grades - last grade accepted + assertParseSuccess(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + ADDRESS_DESC_BOB + + SUBJECT_DESC_BOB + GRADE_DESC_AMY + GRADE_DESC_BOB + EDUCATION_LEVEL_DESC_BOB + + SCHOOL_DESC_BOB + TAG_DESC_FRIEND, new AddTuteeCommand(expectedTutee)); + + // multiple education levels - last education level accepted + assertParseSuccess(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + ADDRESS_DESC_BOB + + SUBJECT_DESC_BOB + GRADE_DESC_BOB + EDUCATION_LEVEL_DESC_AMY + EDUCATION_LEVEL_DESC_BOB + + SCHOOL_DESC_BOB + TAG_DESC_FRIEND, new AddTuteeCommand(expectedTutee)); + + // multiple schools - last school accepted + assertParseSuccess(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + ADDRESS_DESC_BOB + + SUBJECT_DESC_BOB + GRADE_DESC_BOB + EDUCATION_LEVEL_DESC_BOB + SCHOOL_DESC_AMY + + SCHOOL_DESC_BOB + TAG_DESC_FRIEND, new AddTuteeCommand(expectedTutee)); + + // multiple tags - all accepted + Tutee expectedTuteeMultipleTags = new TuteeBuilder().withName(VALID_NAME_BOB).withPhone(VALID_PHONE_BOB) + .withEmail(VALID_EMAIL_BOB).withAddress(VALID_ADDRESS_BOB).withSubject(VALID_SUBJECT_BOB) + .withGrade(VALID_GRADE_BOB).withEducationLevel(VALID_EDUCATION_LEVEL_BOB).withSchool(VALID_SCHOOL_BOB) + .withTags(VALID_TAG_FRIEND, VALID_TAG_HUSBAND).build(); + assertParseSuccess(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + ADDRESS_DESC_BOB + + SUBJECT_DESC_BOB + GRADE_DESC_BOB + EDUCATION_LEVEL_DESC_BOB + SCHOOL_DESC_BOB + TAG_DESC_FRIEND + + TAG_DESC_HUSBAND + TAG_DESC_FRIEND, new AddTuteeCommand(expectedTuteeMultipleTags)); + } + + @Test + public void parse_optionalFieldsMissing_success() { + // zero tags + Tutee expectedTutee = new TuteeBuilder().withName(VALID_NAME_AMY).withPhone(VALID_PHONE_AMY) + .withEmail(VALID_EMAIL_AMY).withAddress(VALID_ADDRESS_AMY).withSubject(VALID_SUBJECT_AMY) + .withGrade(VALID_GRADE_AMY).withEducationLevel(VALID_EDUCATION_LEVEL_AMY) + .withSchool(VALID_SCHOOL_AMY).withTags().build(); + assertParseSuccess(parser, NAME_DESC_AMY + PHONE_DESC_AMY + EMAIL_DESC_AMY + ADDRESS_DESC_AMY + + SUBJECT_DESC_AMY + GRADE_DESC_AMY + EDUCATION_LEVEL_DESC_AMY + SCHOOL_DESC_AMY, + new AddTuteeCommand(expectedTutee)); + } + + @Test + public void parse_compulsoryFieldMissing_failure() { + String expectedMessage = String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddTuteeCommand.MESSAGE_USAGE); + + // missing name prefix + assertParseFailure(parser, VALID_NAME_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + ADDRESS_DESC_BOB + + SUBJECT_DESC_BOB + GRADE_DESC_BOB + EDUCATION_LEVEL_DESC_BOB + SCHOOL_DESC_BOB, + expectedMessage); + + // missing phone prefix + assertParseFailure(parser, NAME_DESC_BOB + VALID_PHONE_BOB + EMAIL_DESC_BOB + ADDRESS_DESC_BOB + + SUBJECT_DESC_BOB + GRADE_DESC_BOB + EDUCATION_LEVEL_DESC_BOB + SCHOOL_DESC_BOB, + expectedMessage); + + // missing email prefix + assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + VALID_EMAIL_BOB + ADDRESS_DESC_BOB + + SUBJECT_DESC_BOB + GRADE_DESC_BOB + EDUCATION_LEVEL_DESC_BOB + SCHOOL_DESC_BOB, + expectedMessage); + + // missing address prefix + assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + VALID_ADDRESS_BOB + + SUBJECT_DESC_BOB + GRADE_DESC_BOB + EDUCATION_LEVEL_DESC_BOB + SCHOOL_DESC_BOB, + expectedMessage); + + // all prefixes missing + assertParseFailure(parser, VALID_NAME_BOB + VALID_PHONE_BOB + VALID_EMAIL_BOB + VALID_ADDRESS_BOB + + SUBJECT_DESC_BOB + GRADE_DESC_BOB + EDUCATION_LEVEL_DESC_BOB + SCHOOL_DESC_BOB, + expectedMessage); + + // missing subject prefix + assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + ADDRESS_DESC_BOB + + GRADE_DESC_BOB + EDUCATION_LEVEL_DESC_BOB + SCHOOL_DESC_BOB, + expectedMessage); + + // missing grade prefix + assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + ADDRESS_DESC_BOB + + SUBJECT_DESC_BOB + EDUCATION_LEVEL_DESC_BOB + SCHOOL_DESC_BOB, + expectedMessage); + + // missing education level prefix + assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + ADDRESS_DESC_BOB + + SUBJECT_DESC_BOB + GRADE_DESC_BOB + SCHOOL_DESC_BOB, + expectedMessage); + + // missing school prefix + assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + ADDRESS_DESC_BOB + + SUBJECT_DESC_BOB + GRADE_DESC_BOB + EDUCATION_LEVEL_DESC_BOB, + expectedMessage); + } + + @Test + public void parse_invalidValue_failure() { + // invalid name + assertParseFailure(parser, INVALID_NAME_DESC + PHONE_DESC_BOB + EMAIL_DESC_BOB + ADDRESS_DESC_BOB + + SUBJECT_DESC_BOB + GRADE_DESC_BOB + EDUCATION_LEVEL_DESC_BOB + SCHOOL_DESC_BOB + + TAG_DESC_HUSBAND + TAG_DESC_FRIEND, Name.MESSAGE_NAME_CONSTRAINTS); + + // invalid phone + assertParseFailure(parser, NAME_DESC_BOB + INVALID_PHONE_DESC + EMAIL_DESC_BOB + ADDRESS_DESC_BOB + + SUBJECT_DESC_BOB + GRADE_DESC_BOB + EDUCATION_LEVEL_DESC_BOB + SCHOOL_DESC_BOB + + TAG_DESC_HUSBAND + TAG_DESC_FRIEND, Phone.MESSAGE_PHONE_CONSTRAINTS); + + // invalid email + assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + INVALID_EMAIL_DESC + ADDRESS_DESC_BOB + + SUBJECT_DESC_BOB + GRADE_DESC_BOB + EDUCATION_LEVEL_DESC_BOB + SCHOOL_DESC_BOB + + TAG_DESC_HUSBAND + TAG_DESC_FRIEND, Email.MESSAGE_EMAIL_CONSTRAINTS); + + // invalid address + assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + INVALID_ADDRESS_DESC + + SUBJECT_DESC_BOB + GRADE_DESC_BOB + EDUCATION_LEVEL_DESC_BOB + SCHOOL_DESC_BOB + + TAG_DESC_HUSBAND + TAG_DESC_FRIEND, Address.MESSAGE_ADDRESS_CONSTRAINTS); + + // invalid subject + assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + ADDRESS_DESC_BOB + + INVALID_SUBJECT_DESC + GRADE_DESC_BOB + EDUCATION_LEVEL_DESC_BOB + SCHOOL_DESC_BOB + + TAG_DESC_HUSBAND + TAG_DESC_FRIEND, Subject.MESSAGE_SUBJECT_CONSTRAINTS); + + // invalid grade + assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + ADDRESS_DESC_BOB + + SUBJECT_DESC_BOB + INVALID_GRADE_DESC + EDUCATION_LEVEL_DESC_BOB + SCHOOL_DESC_BOB + + TAG_DESC_HUSBAND + TAG_DESC_FRIEND, Grade.MESSAGE_GRADE_CONSTRAINTS); + + // invalid education level + assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + ADDRESS_DESC_BOB + + SUBJECT_DESC_BOB + GRADE_DESC_BOB + INVALID_EDUCATION_LEVEL + SCHOOL_DESC_BOB + + TAG_DESC_HUSBAND + TAG_DESC_FRIEND, EducationLevel.MESSAGE_EDUCATION_LEVEL_CONSTRAINTS); + + // invalid school + assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + ADDRESS_DESC_BOB + + SUBJECT_DESC_BOB + GRADE_DESC_BOB + EDUCATION_LEVEL_DESC_BOB + INVALID_SCHOOL + + TAG_DESC_HUSBAND + TAG_DESC_FRIEND, School.MESSAGE_SCHOOL_CONSTRAINTS); + + // invalid tag + assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + ADDRESS_DESC_BOB + + SUBJECT_DESC_BOB + GRADE_DESC_BOB + EDUCATION_LEVEL_DESC_BOB + SCHOOL_DESC_BOB + + INVALID_TAG_DESC + VALID_TAG_FRIEND, Tag.MESSAGE_TAG_CONSTRAINTS); + + // three invalid values, only first invalid value reported + assertParseFailure(parser, INVALID_NAME_DESC + PHONE_DESC_BOB + EMAIL_DESC_BOB + INVALID_ADDRESS_DESC + + SUBJECT_DESC_BOB + GRADE_DESC_BOB + INVALID_EDUCATION_LEVEL + SCHOOL_DESC_BOB, + Name.MESSAGE_NAME_CONSTRAINTS); + + // non-empty preamble + assertParseFailure(parser, PREAMBLE_NON_EMPTY + NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + + ADDRESS_DESC_BOB + SUBJECT_DESC_BOB + GRADE_DESC_BOB + INVALID_EDUCATION_LEVEL + + SCHOOL_DESC_BOB + TAG_DESC_HUSBAND + TAG_DESC_FRIEND, + String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddTuteeCommand.MESSAGE_USAGE)); + } +} +``` +###### \java\seedu\address\logic\parser\ChangeCommandParserTest.java +``` java +public class ChangeCommandParserTest { + private static final String DAY = "d"; + private static final String WEEK = "w"; + private static final String MONTH = "m"; + private static final String YEAR = "y"; + private ChangeCommandParser parser = new ChangeCommandParser(); + private ChangeCommand changeCommand = new ChangeCommand(DAY); // Set an initial time unit to check against + + + @Test + public void parse_validArgs_returnsChangeCommand() throws Exception { + // get the initial time unit, d + String initialTimeUnit = ChangeCommand.getTimeUnit(); + + // Change time unit to w + ChangeCommand expectedTimeUnit = new ChangeCommand(WEEK); + ChangeCommand changeToInitialTimeUnit = new ChangeCommand(initialTimeUnit); // Change to initial time unit + assertEquals(expectedTimeUnit, parser.parse(WEEK)); + + // Change time unit to m + expectedTimeUnit = new ChangeCommand(MONTH); + changeToInitialTimeUnit = new ChangeCommand(initialTimeUnit); // Change to initial time unit + assertEquals(expectedTimeUnit, parser.parse(MONTH)); + + // Change time unit to y + expectedTimeUnit = new ChangeCommand(YEAR); + changeToInitialTimeUnit = new ChangeCommand(initialTimeUnit); // Change to initial time unit + assertEquals(expectedTimeUnit, parser.parse(YEAR)); + } + + @Test + public void parse_invalidArgs_throwsParseException() { + assertParseFailure(parser, "a", String.format(MESSAGE_INVALID_COMMAND_FORMAT, ChangeCommand.MESSAGE_USAGE)); + assertParseFailure(parser, "D", String.format(MESSAGE_INVALID_COMMAND_FORMAT, ChangeCommand.MESSAGE_USAGE)); + assertParseFailure(parser, "@", String.format(MESSAGE_INVALID_COMMAND_FORMAT, ChangeCommand.MESSAGE_USAGE)); + assertParseFailure(parser, "1", String.format(MESSAGE_INVALID_COMMAND_FORMAT, ChangeCommand.MESSAGE_USAGE)); + assertParseFailure(parser, "", String.format(MESSAGE_INVALID_COMMAND_FORMAT, ChangeCommand.MESSAGE_USAGE)); + } + + @Test + public void parse_sameViewPageTimeUnit_throwsSameTimeUnitException() { + assertParseFailure(parser, "d", String.format(ChangeCommand.MESSAGE_SAME_VIEW)); + } + + @Before + public void isValidTimeUnit() { + // null time unit + Assert.assertThrows(NullPointerException.class, () -> ChangeCommandParser.isValidTimeUnit(null)); + + // invalid time unit + assertFalse(ChangeCommandParser.isValidTimeUnit("")); // empty string + assertFalse(ChangeCommandParser.isValidTimeUnit(" ")); // space only + assertFalse(ChangeCommandParser.isValidTimeUnit("#")); // special characters + assertFalse(ChangeCommandParser.isValidTimeUnit("day")); // full time unit name + assertFalse(ChangeCommandParser.isValidTimeUnit("1")); // numbers + assertFalse(ChangeCommandParser.isValidTimeUnit("a")); // contains invalid alphabets + assertFalse(ChangeCommandParser.isValidTimeUnit("D")); // Capital + assertFalse(ChangeCommandParser.isValidTimeUnit(" d ")); // contains space + + // valid time unit + assertTrue(ChangeCommandParser.isValidTimeUnit(DAY)); // day + assertTrue(ChangeCommandParser.isValidTimeUnit(WEEK)); // week + assertTrue(ChangeCommandParser.isValidTimeUnit(MONTH)); // month + assertTrue(ChangeCommandParser.isValidTimeUnit(YEAR)); // year + } + + @Test + public void isTimeUnitClash() { + // All time units' validity are checked in isValidTimeUnit() + + // There is a clash of time unit + assertTrue(ChangeCommandParser.isTimeUnitClash("d")); + + // No clash in time unit + assertFalse(ChangeCommandParser.isTimeUnitClash("w")); + assertFalse(ChangeCommandParser.isTimeUnitClash("m")); + assertFalse(ChangeCommandParser.isTimeUnitClash("y")); + + // change current time unit to w + changeCommand = new ChangeCommand("w"); + + // There is a clash of time unit for w now + assertTrue(ChangeCommandParser.isTimeUnitClash("w")); + + // d is no longer clash + assertFalse(ChangeCommandParser.isTimeUnitClash("d")); + } +} +``` +###### \java\seedu\address\logic\parser\ParserUtilTest.java +``` java + @Test + public void parseSubject_null_throwsNullPointerException() { + Assert.assertThrows(NullPointerException.class, () -> ParserUtil.parseSubject((String) null)); + Assert.assertThrows(NullPointerException.class, () -> ParserUtil.parseSubject((Optional) null)); + } + + @Test + public void parseSubject_invalidValue_throwsIllegalValueException() { + Assert.assertThrows(IllegalValueException.class, () -> ParserUtil.parseSubject(INVALID_SUBJECT)); + Assert.assertThrows(IllegalValueException.class, () -> ParserUtil.parseSubject(Optional.of(INVALID_SUBJECT))); + } + + @Test + public void parseSubject_optionalEmpty_returnsOptionalEmpty() throws Exception { + assertFalse(ParserUtil.parseSubject(Optional.empty()).isPresent()); + } + + @Test + public void parseSubject_validValueWithoutWhitespace_returnsSubject() throws Exception { + Subject expectedSubject = new Subject(VALID_SUBJECT); + assertEquals(expectedSubject, ParserUtil.parseSubject(VALID_SUBJECT)); + assertEquals(Optional.of(expectedSubject), ParserUtil.parseSubject(Optional.of(VALID_SUBJECT))); + } + + @Test + public void parseSubject_validValueWithWhitespace_returnsTrimmedSubject() throws Exception { + String subjectWithWhitespace = WHITESPACE + VALID_SUBJECT + WHITESPACE; + Subject expectedSubject = new Subject(VALID_SUBJECT); + assertEquals(expectedSubject, ParserUtil.parseSubject(subjectWithWhitespace)); + assertEquals(Optional.of(expectedSubject), ParserUtil.parseSubject(Optional.of(subjectWithWhitespace))); + } + + @Test + public void parseGrade_null_throwsNullPointerException() { + Assert.assertThrows(NullPointerException.class, () -> ParserUtil.parseGrade((String) null)); + Assert.assertThrows(NullPointerException.class, () -> ParserUtil.parseGrade((Optional) null)); + } + + @Test + public void parseGrade_invalidValue_throwsIllegalValueException() { + Assert.assertThrows(IllegalValueException.class, () -> ParserUtil.parseGrade(INVALID_GRADE)); + Assert.assertThrows(IllegalValueException.class, () -> ParserUtil.parseGrade(Optional.of(INVALID_GRADE))); + } + + @Test + public void parseGrade_optionalEmpty_returnsOptionalEmpty() throws Exception { + assertFalse(ParserUtil.parseGrade(Optional.empty()).isPresent()); + } + + @Test + public void parseGrade_validValueWithoutWhitespace_returnsGrade() throws Exception { + Grade expectedGrade = new Grade(VALID_GRADE); + assertEquals(expectedGrade, ParserUtil.parseGrade(VALID_GRADE)); + assertEquals(Optional.of(expectedGrade), ParserUtil.parseGrade(Optional.of(VALID_GRADE))); + } + + @Test + public void parseGrade_validValueWithWhitespace_returnsTrimmedGrade() throws Exception { + String gradeWithWhitespace = WHITESPACE + VALID_GRADE + WHITESPACE; + Grade expectedGrade = new Grade(VALID_GRADE); + assertEquals(expectedGrade, ParserUtil.parseGrade(gradeWithWhitespace)); + assertEquals(Optional.of(expectedGrade), ParserUtil.parseGrade(Optional.of(gradeWithWhitespace))); + } + + @Test + public void parseEducationLevel_null_throwsNullPointerException() { + Assert.assertThrows(NullPointerException.class, () -> ParserUtil.parseEducationLevel((String) null)); + Assert.assertThrows(NullPointerException.class, () -> ParserUtil.parseEducationLevel((Optional) null)); + } + + @Test + public void parseEducationLevel_invalidValue_throwsIllegalValueException() { + Assert.assertThrows(IllegalValueException.class, () -> ParserUtil.parseEducationLevel( + INVALID_EDUCATIONAL_LEVEL)); + Assert.assertThrows(IllegalValueException.class, () -> ParserUtil.parseEducationLevel( + Optional.of(INVALID_EDUCATIONAL_LEVEL))); + } + + @Test + public void parseEducationLevel_optionalEmpty_returnsOptionalEmpty() throws Exception { + assertFalse(ParserUtil.parseEducationLevel(Optional.empty()).isPresent()); + } + + @Test + public void parseEducationLevel_validValueWithoutWhitespace_returnsEducationLevel() throws Exception { + EducationLevel expectedEducationLevel = new EducationLevel(VALID_EDUCATIONAL_LEVEL); + assertEquals(expectedEducationLevel, ParserUtil.parseEducationLevel(VALID_EDUCATIONAL_LEVEL)); + assertEquals(Optional.of(expectedEducationLevel), ParserUtil.parseEducationLevel( + Optional.of(VALID_EDUCATIONAL_LEVEL))); + } + + @Test + public void parseEducationLevel_validValueWithWhitespace_returnsTrimmedEducationLevel() throws Exception { + String educationLevelWithWhitespace = WHITESPACE + VALID_EDUCATIONAL_LEVEL + WHITESPACE; + EducationLevel expectedEducationLevel = new EducationLevel(VALID_EDUCATIONAL_LEVEL); + assertEquals(expectedEducationLevel, ParserUtil.parseEducationLevel(educationLevelWithWhitespace)); + assertEquals(Optional.of(expectedEducationLevel), ParserUtil.parseEducationLevel( + Optional.of(educationLevelWithWhitespace))); + } + + @Test + public void parseSchool_null_throwsNullPointerException() { + Assert.assertThrows(NullPointerException.class, () -> ParserUtil.parseSchool((String) null)); + Assert.assertThrows(NullPointerException.class, () -> ParserUtil.parseSchool((Optional) null)); + } + + @Test + public void parseSchool_invalidValue_throwsIllegalValueException() { + Assert.assertThrows(IllegalValueException.class, () -> ParserUtil.parseSchool(INVALID_SCHOOL)); + Assert.assertThrows(IllegalValueException.class, () -> ParserUtil.parseSchool(Optional.of(INVALID_SCHOOL))); + } + + @Test + public void parseSchool_optionalEmpty_returnsOptionalEmpty() throws Exception { + assertFalse(ParserUtil.parseSchool(Optional.empty()).isPresent()); + } + + @Test + public void parseSchool_validValueWithoutWhitespace_returnsSchool() throws Exception { + School expectedSchool = new School(VALID_SCHOOL); + assertEquals(expectedSchool, ParserUtil.parseSchool(VALID_SCHOOL)); + assertEquals(Optional.of(expectedSchool), ParserUtil.parseSchool(Optional.of(VALID_SCHOOL))); + } + + @Test + public void parseSchool_validValueWithWhitespace_returnsTrimmedSchool() throws Exception { + String schoolWithWhitespace = WHITESPACE + VALID_SCHOOL + WHITESPACE; + School expectedSchool = new School(VALID_SCHOOL); + assertEquals(expectedSchool, ParserUtil.parseSchool(schoolWithWhitespace)); + assertEquals(Optional.of(expectedSchool), ParserUtil.parseSchool(Optional.of(schoolWithWhitespace))); + } + + @Test + public void parseTimeUnit_null_throwsNullPointerException() { + Assert.assertThrows(NullPointerException.class, () -> ParserUtil.parseTimeUnit(null)); + } + + @Test + public void parseTimeUnit_invalidValue_throwsIllegalValueException() { + Assert.assertThrows(IllegalValueException.class, () -> ParserUtil.parseTimeUnit(INVALID_TIME_UNIT)); + } + + @Test + public void parseTimeUnit_validValueWithoutWhitespace_returnsTimeUnit() throws Exception { + String expectedTimeUnit = VALID_TIME_UNIT; + assertEquals(expectedTimeUnit, ParserUtil.parseTimeUnit(VALID_TIME_UNIT)); + } + + @Test + public void parseTimeUnit_validValueWithWhitespace_returnsTrimmedTimeUnit() throws Exception { + String timeUnitWithWhitespace = WHITESPACE + VALID_TIME_UNIT + WHITESPACE; + assertEquals(VALID_TIME_UNIT, ParserUtil.parseTimeUnit(timeUnitWithWhitespace)); + } + +``` +###### \java\seedu\address\model\personal\PersonalTaskTest.java +``` java +public class PersonalTaskTest { + + @BeforeClass + public static void setupBeforeClass() { + new TypicalCalendarEntries(); + } + + @Test + public void constructor_validArgs_success() { + PersonalTask personalTask = new PersonalTask(VALID_START_DATE_TIME, + VALID_DURATION, VALID_DESCRIPTION); + Entry actualEntry = personalTask.getEntry(); + Entry expectedEntry = getPersonalEntry(); + + // To match the ID of the same entry + actualEntry.setId("0"); + expectedEntry.setId("0"); + + assertEquals(VALID_START_DATE_TIME, personalTask.getTaskDateTime()); + assertEquals(VALID_DURATION, personalTask.getDuration()); + assertEquals(VALID_DESCRIPTION, personalTask.getDescription()); + assertEquals(expectedEntry, actualEntry); + } +} +``` +###### \java\seedu\address\model\tutee\EducationLevelTest.java +``` java +public class EducationLevelTest { + + @Test + public void constructor_null_throwsNullPointerException() { + Assert.assertThrows(NullPointerException.class, () -> new EducationLevel(null)); + } + + @Test + public void constructor_invalidEducationLevel_throwsIllegalArgumentException() { + String invalidEducationLevel = ""; + Assert.assertThrows(IllegalArgumentException.class, () -> new EducationLevel(invalidEducationLevel)); + } + + @Test + public void isValidPhone() { + // null phone number + Assert.assertThrows(NullPointerException.class, () -> EducationLevel.isValidEducationLevel(null)); + + // invalid phone numbers + assertFalse(EducationLevel.isValidEducationLevel("")); // empty string + assertFalse(EducationLevel.isValidEducationLevel(" ")); // spaces only + assertFalse(EducationLevel.isValidEducationLevel("91")); // numbers + assertFalse(EducationLevel.isValidEducationLevel("university")); // not the specified education level + assertFalse(EducationLevel.isValidEducationLevel("primary5")); // contains number + assertFalse(EducationLevel.isValidEducationLevel("primary@")); // contains special characters + + // valid phone numbers + assertTrue(EducationLevel.isValidEducationLevel("primary")); // primary school + assertTrue(EducationLevel.isValidEducationLevel("secondary")); // secondary school + assertTrue(EducationLevel.isValidEducationLevel("junior college")); // junior college + assertTrue(EducationLevel.isValidEducationLevel("SeCoNdaRy")); // Capital + } + +} +``` +###### \java\seedu\address\model\tutee\GradeTest.java +``` java +public class GradeTest { + + @Test + public void constructor_null_throwsNullPointerException() { + Assert.assertThrows(NullPointerException.class, () -> new Grade(null)); + } + + @Test + public void constructor_invalidGrade_throwsIllegalArgumentException() { + String invalidGrade = ""; + Assert.assertThrows(IllegalArgumentException.class, () -> new Grade(invalidGrade)); + } + + @Test + public void isValidGrade() { + // null subject grade + Assert.assertThrows(NullPointerException.class, () -> Grade.isValidGrade(null)); + + // invalid subject grade + assertFalse(Grade.isValidGrade("")); // empty string + assertFalse(Grade.isValidGrade(" ")); // spaces only + assertFalse(Grade.isValidGrade("9112")); // only contains numbers + assertFalse(Grade.isValidGrade("pass")); // more than 2 alphabet + assertFalse(Grade.isValidGrade("+B")); // special character before alphabet + assertFalse(Grade.isValidGrade("B -")); // spaces within digits + + // valid subject grade + assertTrue(Grade.isValidGrade("A+")); // 1 alphabet followed by a special character + assertTrue(Grade.isValidGrade("B")); // only 1 alphabet + assertTrue(Grade.isValidGrade("b")); // small letter + assertTrue(Grade.isValidGrade("C5")); // number after alphabet + } + +} +``` +###### \java\seedu\address\model\tutee\SchoolTest.java +``` java +public class SchoolTest { + + @Test + public void constructor_null_throwsNullPointerException() { + Assert.assertThrows(NullPointerException.class, () -> new School(null)); + } + + @Test + public void constructor_invalidSchool_throwsIllegalArgumentException() { + String invalidSchool = ""; + Assert.assertThrows(IllegalArgumentException.class, () -> new School(invalidSchool)); + } + + @Test + public void isValidSchool() { + // null school name + Assert.assertThrows(NullPointerException.class, () -> School.isValidSchool(null)); + + // invalid school name + assertFalse(School.isValidSchool("")); // empty string + assertFalse(School.isValidSchool(" ")); // spaces only + assertFalse(School.isValidSchool("^")); // only non-alphabetic characters + assertFalse(School.isValidSchool("bedok primary school*")); // contains non-alphabetic characters + assertFalse(School.isValidSchool("911")); // numbers only + assertFalse(School.isValidSchool("bedok12 secondary school")); // contains numbers + + // valid school name + assertTrue(School.isValidSchool("victoria junior college")); // alphabets only + assertTrue(School.isValidSchool("Victoria Junior College")); // with capital letters + assertTrue(School.isValidSchool("The longest name school primary school")); // long subject name + } + +} +``` +###### \java\seedu\address\model\tutee\SubjectTest.java +``` java +public class SubjectTest { + + @Test + public void constructor_null_throwsNullPointerException() { + Assert.assertThrows(NullPointerException.class, () -> new Subject(null)); + } + + @Test + public void constructor_invalidSubject_throwsIllegalArgumentException() { + String invalidSubject = ""; + Assert.assertThrows(IllegalArgumentException.class, () -> new Subject(invalidSubject)); + } + + @Test + public void isValidSubject() { + // null subject + Assert.assertThrows(NullPointerException.class, () -> Subject.isValidSubject(null)); + + // invalid subject name + assertFalse(Subject.isValidSubject("")); // empty string + assertFalse(Subject.isValidSubject(" ")); // spaces only + assertFalse(Subject.isValidSubject("^")); // only non-alphabetic characters + assertFalse(Subject.isValidSubject("economics*")); // contains non-alphabetic characters + assertFalse(Subject.isValidSubject("911")); // numbers only + assertFalse(Subject.isValidSubject("math12")); // contains numbers + + // valid subject name + assertTrue(Subject.isValidSubject("social studies")); // alphabets only + assertTrue(Subject.isValidSubject("Social Studies")); // with capital letters + assertTrue(Subject.isValidSubject("introduction to fluid dynamics")); // long subject name + } + +} +``` +###### \java\seedu\address\model\tutee\TuitionTaskTest.java +``` java +public class TuitionTaskTest { + + @BeforeClass + public static void setupBeforeClass() { + new TypicalCalendarEntries(); + } + + @Test + public void constructor_validArgs_success() { + TuitionTask tuitionTask = new TuitionTask(VALID_NAME, VALID_START_DATE_TIME, + VALID_DURATION, VALID_DESCRIPTION); + Entry actualEntry = tuitionTask.getEntry(); + Entry expectedEntry = getTuitionEntry(); + + // To match the ID of the same entry + actualEntry.setId("0"); + expectedEntry.setId("0"); + + assertEquals(VALID_NAME, tuitionTask.getPerson()); + assertEquals(VALID_START_DATE_TIME, tuitionTask.getTaskDateTime()); + assertEquals(VALID_DURATION, tuitionTask.getDuration()); + assertEquals(VALID_DESCRIPTION, tuitionTask.getDescription()); + assertEquals(expectedEntry, actualEntry); + } +} +``` +###### \java\seedu\address\model\UniqueTaskListTest.java +``` java +public class UniqueTaskListTest { + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + private DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd/MM/uuuu HH:mm") + .withResolverStyle(ResolverStyle.STRICT); + private UniqueTaskList uniqueTaskList = new UniqueTaskList(); + + @BeforeClass + public static void setupBeforeClass() { + SystemTestSetupHelper.initialize(); + } + + @Test + public void asObservableList_modifyList_throwsUnsupportedOperationException() { + UniqueTaskList uniqueTaskList = new UniqueTaskList(); + thrown.expect(UnsupportedOperationException.class); + uniqueTaskList.asObservableList().remove(0); + } + + @Test + public void addNewTask_clashes_throwsTimingClashException() { + try { + createTaskList(); + } catch (TimingClashException e) { + throw new AssertionError("Should not have any clashed timing"); + } + + // New task starts at the same time as an existing task + Assert.assertThrows(TimingClashException.class, () -> + uniqueTaskList.add(new PersonalTask( + LocalDateTime.parse("11/01/2011 22:00", formatter), "2h0m", "Homework 1"))); + + // New task starts during an existing task + Assert.assertThrows(TimingClashException.class, () -> + uniqueTaskList.add(new PersonalTask( + LocalDateTime.parse("15/01/2011 22:30", formatter), "2h0m", "Homework 2"))); + + // New task ends at the same time as an existing task + Assert.assertThrows(TimingClashException.class, () -> + uniqueTaskList.add(new PersonalTask( + LocalDateTime.parse("13/01/2011 11:30", formatter), "0h30m", "Homework 3"))); + + // New task ends during an existing task + Assert.assertThrows(TimingClashException.class, () -> + uniqueTaskList.add(new PersonalTask( + LocalDateTime.parse("13/01/2011 10:00", formatter), "1h30m", "Homework 4"))); + + // New task is within an existing task completely + Assert.assertThrows(TimingClashException.class, () -> + uniqueTaskList.add(new TuitionTask( + "Anne", LocalDateTime.parse("15/01/2011 22:30", formatter), "1h30m", "Assignment"))); + + // Existing task is within the new task completely + Assert.assertThrows(TimingClashException.class, () -> + uniqueTaskList.add(new TuitionTask( + "Ben", LocalDateTime.parse("11/01/2011 21:00", formatter), "4h0m", "Revision"))); + } + + /** + * Generates a list of existing tasks + */ + private void createTaskList() throws TimingClashException { + uniqueTaskList.add(new TuitionTask("Anne", + LocalDateTime.parse("11/01/2011 22:00", formatter), "1h30m", "tuition 1")); + uniqueTaskList.add(new PersonalTask( + LocalDateTime.parse("15/01/2011 22:00", formatter), "2h30m", "personal task 1")); + uniqueTaskList.add(new PersonalTask( + LocalDateTime.parse("13/01/2011 11:00", formatter), "1h0m", "personal task 2")); + } +} +``` +###### \java\seedu\address\testutil\TuteeBuilder.java +``` java +/** + * A utility class to help with building Tutee objects. + */ +public class TuteeBuilder extends PersonBuilder { + public static final String DEFAULT_SUBJECT = "social studies"; + public static final String DEFAULT_GRADE = "B-"; + public static final String DEFAULT_EDUCATION_LEVEL = "primary"; + public static final String DEFAULT_SCHOOL = "fengshan primary school"; + + private Subject subject; + private Grade grade; + private EducationLevel educationLevel; + private School school; + + public TuteeBuilder() { + name = new Name(DEFAULT_NAME); + phone = new Phone(DEFAULT_PHONE); + email = new Email(DEFAULT_EMAIL); + address = new Address(DEFAULT_ADDRESS); + subject = new Subject(DEFAULT_SUBJECT); + grade = new Grade(DEFAULT_GRADE); + educationLevel = new EducationLevel(DEFAULT_EDUCATION_LEVEL); + school = new School(DEFAULT_SCHOOL); + tags = SampleDataUtil.getTagSet(DEFAULT_TAGS); + tags.add(new Tag("Tutee")); + } + + /** + * Initializes the PersonBuilder with the data of {@code personToCopy}. + */ + public TuteeBuilder(Tutee tuteeToCopy) { + name = tuteeToCopy.getName(); + phone = tuteeToCopy.getPhone(); + email = tuteeToCopy.getEmail(); + address = tuteeToCopy.getAddress(); + subject = tuteeToCopy.getSubject(); + grade = tuteeToCopy.getGrade(); + educationLevel = tuteeToCopy.getEducationLevel(); + school = tuteeToCopy.getSchool(); + tags = new HashSet<>(tuteeToCopy.getTags()); + } + + /** + * Sets the {@code Name} of the {@code Tutee} that we are building. + */ + public TuteeBuilder withName(String name) { + this.name = new Name(name); + return this; + } + + /** + * Parses the {@code tags} into a {@code Set} and set it to the {@code Tutee} that we are building. + */ + public TuteeBuilder withTags(String ... tags) { + this.tags = SampleDataUtil.getTagSet(tags); + this.tags.add(new Tag("Tutee")); + return this; + } + + /** + * Sets the {@code Address} of the {@code Tutee} that we are building. + */ + public TuteeBuilder withAddress(String address) { + this.address = new Address(address); + return this; + } + + /** + * Sets the {@code Phone} of the {@code Tutee} that we are building. + */ + public TuteeBuilder withPhone(String phone) { + this.phone = new Phone(phone); + return this; + } + + /** + * Sets the {@code Email} of the {@code Tutee} that we are building. + */ + public TuteeBuilder withEmail(String email) { + this.email = new Email(email); + return this; + } + + /** + * Sets the {@code Subject} of the {@code Tutee} that we are building. + */ + public TuteeBuilder withSubject(String subject) { + this.subject = new Subject(subject); + return this; + } + /** + * Sets the {@code Grade} of the {@code Tutee} that we are building. + */ + public TuteeBuilder withGrade(String grade) { + this.grade = new Grade(grade); + return this; + } + /** + * Sets the {@code EducationLevel} of the {@code Tutee} that we are building. + */ + public TuteeBuilder withEducationLevel(String educationLevel) { + this.educationLevel = new EducationLevel(educationLevel); + return this; + } + /** + * Sets the {@code School} of the {@code Tutee} that we are building. + */ + public TuteeBuilder withSchool(String school) { + this.school = new School(school); + return this; + } + + + public Tutee build() { + return new Tutee(name, phone, email, address, subject, grade, educationLevel, school, tags); + } + +} +``` +###### \java\seedu\address\testutil\TuteeUtil.java +``` java +/** + * A utility class for Tutee. + */ +public class TuteeUtil { + + /** + * Returns an addtutee command string for adding the {@code tutee}. + */ + public static String getAddTuteeCommand(Tutee tutee) { + return AddTuteeCommand.COMMAND_WORD + " " + getTuteeDetails(tutee); + } + + /** + * Returns the part of command string for the given {@code tutee}'s details. + */ + public static String getTuteeDetails(Tutee tutee) { + StringBuilder sb = new StringBuilder(); + sb.append(PREFIX_NAME + tutee.getName().fullName + " "); + sb.append(PREFIX_PHONE + tutee.getPhone().value + " "); + sb.append(PREFIX_EMAIL + tutee.getEmail().value + " "); + sb.append(PREFIX_ADDRESS + tutee.getAddress().value + " "); + sb.append(PREFIX_SUBJECT + tutee.getSubject().subject + " "); + sb.append(PREFIX_GRADE + tutee.getGrade().grade + " "); + sb.append(PREFIX_EDUCATION_LEVEL + tutee.getEducationLevel().educationLevel + " "); + sb.append(PREFIX_SCHOOL + tutee.getSchool().school + " "); + tutee.getTags().stream().forEach( + s -> sb.append(PREFIX_TAG + s.tagName + " ") + ); + return sb.toString(); + } +} +``` +###### \java\seedu\address\testutil\typicaladdressbook\TypicalTutees.java +``` java +/** + * A utility class containing a list of {@code Tutee} objects to be used in tests. + */ +public class TypicalTutees { + + public static final Tutee ALICETUTEE = new TuteeBuilder().withName("Alice Pauline") + .withAddress("123, Jurong West Ave 6, #08-111").withEmail("alice@example.com") + .withPhone("85355255").withSubject("mathematics").withGrade("C+").withEducationLevel("secondary") + .withSchool("nanhua high school").withTags("friends").build(); + public static final Tutee CARLTUTEE = new TuteeBuilder().withName("Carl Kurz").withPhone("95352563") + .withEmail("heinz@example.com").withAddress("wall street").withSubject("history").withGrade("B") + .withEducationLevel("secondary").withSchool("wall street high school").build(); + + // Manually added + public static final Tutee HOONTUTEE = new TuteeBuilder().withName("Hoon Meier").withPhone("8482424") + .withEmail("stefan@example.com").withAddress("little india").withSubject("economics").withGrade("A1") + .withEducationLevel("secondary").withSchool("changi secondary school").build(); + public static final Tutee IDATUTEE = new TuteeBuilder().withName("Ida Mueller").withPhone("8482131") + .withEmail("hans@example.com").withAddress("chicago ave").withSubject("english").withGrade("B3") + .withEducationLevel("secondary").withSchool("tanjong katong secondary school").build(); + + // Manually added - Tutee's details found in {@code CommandTestUtil} + public static final Tutee AMYTUTEE = new TuteeBuilder().withName(VALID_NAME_AMY).withPhone(VALID_PHONE_AMY) + .withEmail(VALID_EMAIL_AMY).withAddress(VALID_ADDRESS_AMY).withSubject(VALID_SUBJECT_AMY) + .withGrade(VALID_GRADE_AMY).withEducationLevel(VALID_EDUCATION_LEVEL_AMY).withSchool(VALID_SCHOOL_AMY) + .withTags(VALID_TAG_FRIEND).build(); + public static final Tutee BOBTUTEE = new TuteeBuilder().withName(VALID_NAME_BOB).withPhone(VALID_PHONE_BOB) + .withEmail(VALID_EMAIL_BOB).withAddress(VALID_ADDRESS_BOB).withSubject(VALID_SUBJECT_BOB) + .withGrade(VALID_GRADE_BOB).withEducationLevel(VALID_EDUCATION_LEVEL_BOB).withSchool(VALID_SCHOOL_BOB) + .withTags(VALID_TAG_HUSBAND, VALID_TAG_FRIEND).build(); + + public static final String KEYWORD_MATCHING_MEIER = "Meier"; // A keyword that matches MEIER + + private TypicalTutees() {} // prevents instantiation + + public static List getTypicalPersonsAndTutees() { + + return new ArrayList<>(Arrays.asList(ALICETUTEE, DANIEL, AMYTUTEE, BOBTUTEE)); + } +} +``` +###### \java\seedu\address\testutil\TypicalCalendarEntries.java +``` java +/** + * A utility class containing a list of {@code Entry} objects to be used in tests. + */ +public class TypicalCalendarEntries { + public static final String VALID_NAME = "Jason"; + public static final String VALID_DURATION = "1h30m"; + public static final String VALID_DESCRIPTION = "homework 1"; + + private static final String VALID_STRING_START_DATE_TIME = "01/04/2018 11:00"; + private static final String VALID_STRING_END_DATE_TIME = "01/04/2018 12:30"; + private static DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd/MM/uuuu HH:mm") + .withResolverStyle(ResolverStyle.STRICT); + + public static final LocalDateTime VALID_START_DATE_TIME = + LocalDateTime.parse(VALID_STRING_START_DATE_TIME, formatter); + public static final LocalDateTime VALID_END_DATE_TIME = + LocalDateTime.parse(VALID_STRING_END_DATE_TIME, formatter); + private static Entry validTuitionEntry; + private static Entry validPersonalEntry; + + /** + * Creates valid calendar entry + * + */ + public TypicalCalendarEntries() { + Interval interval = new Interval(VALID_START_DATE_TIME, VALID_END_DATE_TIME); + createTuitionEntry(interval); + createPersonalEntry(interval); + } + + /** + * Creates a valid tuition calendar entry + * + * @param interval of the entry + */ + private void createTuitionEntry(Interval interval) { + validTuitionEntry = new Entry(VALID_NAME); + validTuitionEntry.setInterval(interval); + } + + /** + * Creates a valid personal calendar entry + * + * @param interval of the entry + */ + private void createPersonalEntry(Interval interval) { + validPersonalEntry = new Entry(VALID_DESCRIPTION); + validPersonalEntry.setInterval(interval); + } + + public static Entry getTuitionEntry() { + return validTuitionEntry; + } + + public static Entry getPersonalEntry() { + return validPersonalEntry; + } +} +``` +###### \java\seedu\address\ui\CalendarPanelTest.java +``` java +public class CalendarPanelTest extends GuiUnitTest { + + private CalendarPanel calendarPanel; + private CalendarPanelHandle calendarPanelHandle; + + @Before + public void setUp() { + calendarPanel = new CalendarPanel(); + guiRobot.interact(() -> calendarPanel = new CalendarPanel()); + uiPartRule.setUiPart(calendarPanel); + + calendarPanelHandle = new CalendarPanelHandle(calendarPanel.getRoot()); + } + + @Test + public void display() { + // calendar view page is not null + assertNotNull(calendarPanel.getRoot()); + + // default view page of calendar + assertEquals(calendarPanel.getRoot().getSelectedPage(), calendarPanelHandle.getDefaultCalendarViewPage()); + + // view page changes to week + CalendarPanel.changeViewPage('w'); + assertEquals(calendarPanelHandle.getCurrentCalendarViewPage(), calendarPanelHandle.getWeekViewPage()); + + // view page changes to month + CalendarPanel.changeViewPage('m'); + assertEquals(calendarPanelHandle.getCurrentCalendarViewPage(), calendarPanelHandle.getMonthViewPage()); + + // view page changes to year + CalendarPanel.changeViewPage('y'); + assertEquals(calendarPanelHandle.getCurrentCalendarViewPage(), calendarPanelHandle.getYearViewPage()); + + // view page changes to day (default) + CalendarPanel.changeViewPage('d'); + assertEquals(calendarPanelHandle.getCurrentCalendarViewPage(), + calendarPanelHandle.getDefaultCalendarViewPage()); + } +} +``` +###### \java\systemtests\AddressBookSystemTest.java +``` java + /** + * Asserts that the starting calendar view of the application is correct. + */ + private void assertStartingCalendarViewPageIsCorrect() { + assertEquals(getCalendarPanel().getDefaultCalendarViewPage(), getCalendarPanel().getCurrentCalendarViewPage()); + } +``` +###### \java\systemtests\AddTuteeCommandSystemTest.java +``` java +public class AddTuteeCommandSystemTest extends AddressBookSystemTest { + + @Test + public void addtutee() throws Exception { + Model model = getModel(); + + /* ------------------------ Perform add operations on the shown unfiltered list ----------------------------- */ + + /* Case: add a person without tags to a non-empty address book, command with leading spaces and trailing spaces + * -> added + */ + Tutee toAdd = AMYTUTEE; + String command = " " + AddTuteeCommand.COMMAND_WORD + " " + NAME_DESC_AMY + " " + PHONE_DESC_AMY + " " + + EMAIL_DESC_AMY + " " + ADDRESS_DESC_AMY + " " + SUBJECT_DESC_AMY + GRADE_DESC_AMY + + EDUCATION_LEVEL_DESC_AMY + SCHOOL_DESC_AMY + TAG_DESC_FRIEND + " "; + assertCommandSuccess(command, toAdd); + + /* Case: undo adding Amy to the list -> Amy deleted */ + command = UndoCommand.COMMAND_WORD; + String expectedResultMessage = UndoCommand.MESSAGE_SUCCESS; + assertCommandSuccess(command, model, expectedResultMessage); + + /* Case: redo adding Amy to the list -> Amy added again */ + command = RedoCommand.COMMAND_WORD; + model.addPerson(toAdd); + expectedResultMessage = RedoCommand.MESSAGE_SUCCESS; + assertCommandSuccess(command, model, expectedResultMessage); + + /* Case: add a tutee with all fields same as another tutee in the address book except name -> added */ + toAdd = new TuteeBuilder().withName(VALID_NAME_BOB).withPhone(VALID_PHONE_AMY).withEmail(VALID_EMAIL_AMY) + .withAddress(VALID_ADDRESS_AMY).withSubject(VALID_SUBJECT_AMY).withGrade(VALID_GRADE_AMY) + .withEducationLevel(VALID_EDUCATION_LEVEL_AMY).withSchool(VALID_SCHOOL_AMY).withTags(VALID_TAG_FRIEND) + .build(); + command = AddTuteeCommand.COMMAND_WORD + NAME_DESC_BOB + PHONE_DESC_AMY + EMAIL_DESC_AMY + ADDRESS_DESC_AMY + + SUBJECT_DESC_AMY + GRADE_DESC_AMY + EDUCATION_LEVEL_DESC_AMY + SCHOOL_DESC_AMY + TAG_DESC_FRIEND; + assertCommandSuccess(command, toAdd); + + /* Case: add a tutee with all fields same as another tutee in the address book except phone -> added */ + toAdd = new TuteeBuilder().withName(VALID_NAME_AMY).withPhone(VALID_PHONE_BOB).withEmail(VALID_EMAIL_AMY) + .withAddress(VALID_ADDRESS_AMY).withTags(VALID_TAG_FRIEND).withSubject(VALID_SUBJECT_AMY) + .withGrade(VALID_GRADE_AMY).withEducationLevel(VALID_EDUCATION_LEVEL_AMY).withSchool(VALID_SCHOOL_AMY) + .withTags(VALID_TAG_FRIEND).build(); + command = AddTuteeCommand.COMMAND_WORD + NAME_DESC_AMY + PHONE_DESC_BOB + EMAIL_DESC_AMY + ADDRESS_DESC_AMY + + SUBJECT_DESC_AMY + GRADE_DESC_AMY + EDUCATION_LEVEL_DESC_AMY + SCHOOL_DESC_AMY + TAG_DESC_FRIEND; + assertCommandSuccess(command, toAdd); + + /* Case: add a tutee with all fields same as another tutee in the address book except email -> added */ + toAdd = new TuteeBuilder().withName(VALID_NAME_AMY).withPhone(VALID_PHONE_AMY).withEmail(VALID_EMAIL_BOB) + .withAddress(VALID_ADDRESS_AMY).withSubject(VALID_SUBJECT_AMY).withGrade(VALID_GRADE_AMY) + .withEducationLevel(VALID_EDUCATION_LEVEL_AMY).withSchool(VALID_SCHOOL_AMY).withTags(VALID_TAG_FRIEND) + .withTags(VALID_TAG_FRIEND).build(); + command = AddTuteeCommand.COMMAND_WORD + NAME_DESC_AMY + PHONE_DESC_AMY + EMAIL_DESC_BOB + ADDRESS_DESC_AMY + + SUBJECT_DESC_AMY + GRADE_DESC_AMY + EDUCATION_LEVEL_DESC_AMY + SCHOOL_DESC_AMY + TAG_DESC_FRIEND; + assertCommandSuccess(command, toAdd); + + /* Case: add a person with all fields same as another person in the address book except address -> added */ + toAdd = new TuteeBuilder().withName(VALID_NAME_AMY).withPhone(VALID_PHONE_AMY).withEmail(VALID_EMAIL_AMY) + .withAddress(VALID_ADDRESS_BOB).withSubject(VALID_SUBJECT_AMY).withGrade(VALID_GRADE_AMY) + .withEducationLevel(VALID_EDUCATION_LEVEL_AMY).withSchool(VALID_SCHOOL_AMY).withTags(VALID_TAG_FRIEND) + .withTags(VALID_TAG_FRIEND).build(); + command = AddTuteeCommand.COMMAND_WORD + NAME_DESC_AMY + PHONE_DESC_AMY + EMAIL_DESC_AMY + ADDRESS_DESC_BOB + + SUBJECT_DESC_AMY + GRADE_DESC_AMY + EDUCATION_LEVEL_DESC_AMY + SCHOOL_DESC_AMY + TAG_DESC_FRIEND; + assertCommandSuccess(command, toAdd); + + /* Case: add to empty address book -> added */ + deleteAllPersons(); + assertCommandSuccess(ALICETUTEE); + + /* Case: add a person with tags, command with parameters in random order -> added */ + toAdd = BOBTUTEE; + command = AddTuteeCommand.COMMAND_WORD + TAG_DESC_FRIEND + PHONE_DESC_BOB + ADDRESS_DESC_BOB + NAME_DESC_BOB + + TAG_DESC_HUSBAND + EMAIL_DESC_BOB + GRADE_DESC_BOB + SUBJECT_DESC_BOB + EDUCATION_LEVEL_DESC_BOB + + SCHOOL_DESC_BOB; + assertCommandSuccess(command, toAdd); + + /* Case: add a person, missing tags -> added */ + assertCommandSuccess(HOONTUTEE); + + /* -------------------------- Perform add operation on the shown filtered list ------------------------------ */ + + /* Case: filters the person list before adding -> added */ + showPersonsWithName(KEYWORD_MATCHING_MEIER); + assertCommandSuccess(IDATUTEE); + + /* ------------------------ Perform add operation while a person card is selected --------------------------- */ + + /* Case: selects first card in the person list, add a person -> added, card selection remains unchanged */ + selectPerson(Index.fromOneBased(1)); + assertCommandSuccess(CARLTUTEE); + + /* ----------------------------------- Perform invalid add operations --------------------------------------- */ + + /* Case: add a duplicate tutee -> rejected */ + command = TuteeUtil.getAddTuteeCommand(HOONTUTEE); + assertCommandFailure(command, AddTuteeCommand.MESSAGE_DUPLICATE_PERSON); + + /* Case: add a duplicate tutee except with different tags -> rejected */ + // "friends" is an existing tag used in the default model, see TypicalPersons#ALICE + // This test will fail if a new tag that is not in the model is used, see the bug documented in + // AddressBook#addPerson(Person) + command = TuteeUtil.getAddTuteeCommand(HOONTUTEE) + " " + PREFIX_TAG.getPrefix() + "friends"; + assertCommandFailure(command, AddTuteeCommand.MESSAGE_DUPLICATE_PERSON); + + /* Case: missing name -> rejected */ + command = AddTuteeCommand.COMMAND_WORD + PHONE_DESC_AMY + EMAIL_DESC_AMY + ADDRESS_DESC_AMY + + SUBJECT_DESC_AMY + GRADE_DESC_AMY + EDUCATION_LEVEL_DESC_AMY + SCHOOL_DESC_AMY; + assertCommandFailure(command, String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddTuteeCommand.MESSAGE_USAGE)); + + /* Case: missing phone -> rejected */ + command = AddTuteeCommand.COMMAND_WORD + NAME_DESC_AMY + EMAIL_DESC_AMY + ADDRESS_DESC_AMY + + SUBJECT_DESC_AMY + GRADE_DESC_AMY + EDUCATION_LEVEL_DESC_AMY + SCHOOL_DESC_AMY; + assertCommandFailure(command, String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddTuteeCommand.MESSAGE_USAGE)); + + /* Case: missing email -> rejected */ + command = AddTuteeCommand.COMMAND_WORD + NAME_DESC_AMY + PHONE_DESC_AMY + ADDRESS_DESC_AMY + + SUBJECT_DESC_AMY + GRADE_DESC_AMY + EDUCATION_LEVEL_DESC_AMY + SCHOOL_DESC_AMY; + assertCommandFailure(command, String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddTuteeCommand.MESSAGE_USAGE)); + + /* Case: missing address -> rejected */ + command = AddTuteeCommand.COMMAND_WORD + NAME_DESC_AMY + PHONE_DESC_AMY + EMAIL_DESC_AMY + + SUBJECT_DESC_AMY + GRADE_DESC_AMY + EDUCATION_LEVEL_DESC_AMY + SCHOOL_DESC_AMY; + assertCommandFailure(command, String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddTuteeCommand.MESSAGE_USAGE)); + + /* Case: missing subject -> rejected */ + command = AddTuteeCommand.COMMAND_WORD + NAME_DESC_AMY + PHONE_DESC_AMY + EMAIL_DESC_AMY + + ADDRESS_DESC_AMY + GRADE_DESC_AMY + EDUCATION_LEVEL_DESC_AMY + SCHOOL_DESC_AMY; + assertCommandFailure(command, String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddTuteeCommand.MESSAGE_USAGE)); + + /* Case: missing grade -> rejected */ + command = AddTuteeCommand.COMMAND_WORD + NAME_DESC_AMY + PHONE_DESC_AMY + EMAIL_DESC_AMY + + ADDRESS_DESC_AMY + GRADE_DESC_AMY + EDUCATION_LEVEL_DESC_AMY + SCHOOL_DESC_AMY; + assertCommandFailure(command, String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddTuteeCommand.MESSAGE_USAGE)); + + /* Case: missing education level -> rejected */ + command = AddTuteeCommand.COMMAND_WORD + NAME_DESC_AMY + PHONE_DESC_AMY + EMAIL_DESC_AMY + + ADDRESS_DESC_AMY + SUBJECT_DESC_AMY + GRADE_DESC_AMY + SCHOOL_DESC_AMY; + assertCommandFailure(command, String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddTuteeCommand.MESSAGE_USAGE)); + + /* Case: missing school -> rejected */ + command = AddTuteeCommand.COMMAND_WORD + NAME_DESC_AMY + PHONE_DESC_AMY + EMAIL_DESC_AMY + + ADDRESS_DESC_AMY + SUBJECT_DESC_AMY + GRADE_DESC_AMY + EDUCATION_LEVEL_DESC_AMY; + assertCommandFailure(command, String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddTuteeCommand.MESSAGE_USAGE)); + + /* Case: invalid keyword -> rejected */ + command = "addtutees " + TuteeUtil.getTuteeDetails(toAdd); + assertCommandFailure(command, Messages.MESSAGE_UNKNOWN_COMMAND); + + /* Case: invalid name -> rejected */ + command = AddTuteeCommand.COMMAND_WORD + INVALID_NAME_DESC + PHONE_DESC_AMY + EMAIL_DESC_AMY + ADDRESS_DESC_AMY + + SUBJECT_DESC_AMY + GRADE_DESC_AMY + EDUCATION_LEVEL_DESC_AMY + SCHOOL_DESC_AMY; + assertCommandFailure(command, Name.MESSAGE_NAME_CONSTRAINTS); + + /* Case: invalid phone -> rejected */ + command = AddTuteeCommand.COMMAND_WORD + NAME_DESC_AMY + INVALID_PHONE_DESC + EMAIL_DESC_AMY + ADDRESS_DESC_AMY + + SUBJECT_DESC_AMY + GRADE_DESC_AMY + EDUCATION_LEVEL_DESC_AMY + SCHOOL_DESC_AMY; + assertCommandFailure(command, Phone.MESSAGE_PHONE_CONSTRAINTS); + + /* Case: invalid email -> rejected */ + command = AddTuteeCommand.COMMAND_WORD + NAME_DESC_AMY + PHONE_DESC_AMY + INVALID_EMAIL_DESC + ADDRESS_DESC_AMY + + SUBJECT_DESC_AMY + GRADE_DESC_AMY + EDUCATION_LEVEL_DESC_AMY + SCHOOL_DESC_AMY; + assertCommandFailure(command, Email.MESSAGE_EMAIL_CONSTRAINTS); + + /* Case: invalid address -> rejected */ + command = AddTuteeCommand.COMMAND_WORD + NAME_DESC_AMY + PHONE_DESC_AMY + EMAIL_DESC_AMY + INVALID_ADDRESS_DESC + + SUBJECT_DESC_AMY + GRADE_DESC_AMY + EDUCATION_LEVEL_DESC_AMY + SCHOOL_DESC_AMY; + assertCommandFailure(command, Address.MESSAGE_ADDRESS_CONSTRAINTS); + + /* Case: invalid tag -> rejected */ + command = AddTuteeCommand.COMMAND_WORD + NAME_DESC_AMY + PHONE_DESC_AMY + EMAIL_DESC_AMY + ADDRESS_DESC_AMY + + SUBJECT_DESC_AMY + GRADE_DESC_AMY + EDUCATION_LEVEL_DESC_AMY + SCHOOL_DESC_AMY + INVALID_TAG_DESC; + assertCommandFailure(command, Tag.MESSAGE_TAG_CONSTRAINTS); + + /* Case: invalid subject -> rejected */ + command = AddTuteeCommand.COMMAND_WORD + NAME_DESC_AMY + PHONE_DESC_AMY + EMAIL_DESC_AMY + ADDRESS_DESC_AMY + + INVALID_SUBJECT_DESC + GRADE_DESC_AMY + EDUCATION_LEVEL_DESC_AMY + SCHOOL_DESC_AMY + INVALID_TAG_DESC; + assertCommandFailure(command, Subject.MESSAGE_SUBJECT_CONSTRAINTS); + + /* Case: invalid grade -> rejected */ + command = AddTuteeCommand.COMMAND_WORD + NAME_DESC_AMY + PHONE_DESC_AMY + EMAIL_DESC_AMY + ADDRESS_DESC_AMY + + SUBJECT_DESC_AMY + INVALID_GRADE_DESC + EDUCATION_LEVEL_DESC_AMY + SCHOOL_DESC_AMY + INVALID_TAG_DESC; + assertCommandFailure(command, Grade.MESSAGE_GRADE_CONSTRAINTS); + + /* Case: invalid education level -> rejected */ + command = AddTuteeCommand.COMMAND_WORD + NAME_DESC_AMY + PHONE_DESC_AMY + EMAIL_DESC_AMY + ADDRESS_DESC_AMY + + SUBJECT_DESC_AMY + GRADE_DESC_AMY + INVALID_EDUCATION_LEVEL + SCHOOL_DESC_AMY + INVALID_TAG_DESC; + assertCommandFailure(command, EducationLevel.MESSAGE_EDUCATION_LEVEL_CONSTRAINTS); + + /* Case: invalid school -> rejected */ + command = AddTuteeCommand.COMMAND_WORD + NAME_DESC_AMY + PHONE_DESC_AMY + EMAIL_DESC_AMY + ADDRESS_DESC_AMY + + SUBJECT_DESC_AMY + GRADE_DESC_AMY + EDUCATION_LEVEL_DESC_AMY + INVALID_SCHOOL + INVALID_TAG_DESC; + assertCommandFailure(command, School.MESSAGE_SCHOOL_CONSTRAINTS); + } + /** + * Executes the {@code AddTuteeCommand} that adds {@code toAdd} to the model and asserts that the,
+ * 1. Command box displays an empty string.
+ * 2. Command box has the default style class.
+ * 3. Result display box displays the success message of executing {@code AddTuteeCommand} with the details of + * {@code toAdd}.
+ * 4. {@code Model}, {@code Storage} and {@code PersonListPanel} equal to the corresponding components in + * the current model added with {@code toAdd}.
+ * 5. Browser url and selected card remain unchanged.
+ * 6. Status bar's sync status changes.
+ * Verifications 1, 3 and 4 are performed by + * {@code AddressBookSystemTest#assertApplicationDisplaysExpected(String, String, Model)}.
+ * @see AddressBookSystemTest#assertApplicationDisplaysExpected(String, String, Model) + */ + private void assertCommandSuccess(Tutee toAdd) { + assertCommandSuccess(TuteeUtil.getAddTuteeCommand(toAdd), toAdd); + } + + /** + * Performs the same verification as {@code assertCommandSuccess(Tutee)}. Executes {@code command} + * instead. + * @see AddTuteeCommandSystemTest#assertCommandSuccess(Tutee) + */ + private void assertCommandSuccess(String command, Tutee toAdd) { + Model expectedModel = getModel(); + try { + expectedModel.addPerson(toAdd); + } catch (DuplicatePersonException dpe) { + throw new IllegalArgumentException("toAdd already exists in the model."); + } + String expectedResultMessage = String.format(AddTuteeCommand.MESSAGE_SUCCESS, toAdd); + + assertCommandSuccess(command, expectedModel, expectedResultMessage); + } + + /** + * Performs the same verification as {@code assertCommandSuccess(String, Person)} except asserts that + * the,
+ * 1. Result display box displays {@code expectedResultMessage}.
+ * 2. {@code Model}, {@code Storage} and {@code PersonListPanel} equal to the corresponding components in + * {@code expectedModel}.
+ * @see AddTuteeCommandSystemTest#assertCommandSuccess(String, Tutee) + */ + private void assertCommandSuccess(String command, Model expectedModel, String expectedResultMessage) { + executeCommand(command); + assertApplicationDisplaysExpected("", expectedResultMessage, expectedModel); + assertSelectedCardUnchanged(); + assertCommandBoxShowsDefaultStyle(); + assertStatusBarUnchangedExceptSyncStatus(); + } + + /** + * Executes {@code command} and asserts that the,
+ * 1. Command box displays {@code command}.
+ * 2. Command box has the error style class.
+ * 3. Result display box displays {@code expectedResultMessage}.
+ * 4. {@code Model}, {@code Storage} and {@code PersonListPanel} remain unchanged.
+ * 5. Browser url, selected card and status bar remain unchanged.
+ * Verifications 1, 3 and 4 are performed by + * {@code AddressBookSystemTest#assertApplicationDisplaysExpected(String, String, Model)}.
+ * @see AddressBookSystemTest#assertApplicationDisplaysExpected(String, String, Model) + */ + private void assertCommandFailure(String command, String expectedResultMessage) { + Model expectedModel = getModel(); + + executeCommand(command); + assertApplicationDisplaysExpected(command, expectedResultMessage, expectedModel); + assertSelectedCardUnchanged(); + assertCommandBoxShowsErrorStyle(); + assertStatusBarUnchanged(); + } + +} +``` diff --git a/collated/test/a-shakra.md b/collated/test/a-shakra.md new file mode 100644 index 000000000000..e55c93196e0b --- /dev/null +++ b/collated/test/a-shakra.md @@ -0,0 +1,529 @@ +# a-shakra +###### \data\XmlAddressBookStorageTest\invalidAndValidTaskAddressBook.xml +``` xml + + + + exampleTask1 + 3h20m + 05/02/2018 3:20 + + + + exampleTask1 + 3h2m + 05/03/2018 3:20 + + +``` +###### \data\XmlAddressBookStorageTest\invalidTaskAddressBook.xml +``` xml + + + exampleTask1 + 3333333::: + 2018-05-02T03:20 + + +``` +###### \data\XmlSerializableAddressBookTest\invalidTaskAddressBook.xml +``` xml + + + + exampleDescription + 3h2m + 02/05/2018 03:20 + + +``` +###### \data\XmlSerializableAddressBookTest\typicalTasksAddressBook.xml +``` xml + + + exampleTask1 + 3h20m + 02/05/2018T03:20 + + + exampleTask2 + 3h20m + 02/04/2018T13:20 + + + exampleTask3 + 3h20m + 02/06/2018T23:20 + + + exampleTask4 + 3h20m + 02/07/2018T23:20 + + +``` +###### \data\XmlUtilTest\invalidTaskField.xml +``` xml + + exampleTask1 + 3h20m + 2018-00-00T03:20 + +``` +###### \data\XmlUtilTest\missingTaskField.xml +``` xml + + exampleTask1 + 3:20 + +``` +###### \data\XmlUtilTest\tempAddressBook.xml +``` xml + + + 1 + John + Doe + + + + + + + Friends + + + + +``` +###### \data\XmlUtilTest\validAddressBook.xml +``` xml + + exampleTask1 + 3h20m + 2018-05-02T03:20 + + + exampleTask2 + 3h20m + 2018-06-02T03:20 + + + exampleTask3 + 3h20m + 2018-07-02T03:20 + + +``` +###### \data\XmlUtilTest\validTask.xml +``` xml + + exampleTask1 + 3h20m + 02/03/2018T03:20 + +``` +###### \java\seedu\address\commons\util\XmlUtilTest.java +``` java +public class XmlUtilTest { + + private static final String TEST_DATA_FOLDER = FileUtil.getPath("src/test/data/XmlUtilTest/"); + private static final File EMPTY_FILE = new File(TEST_DATA_FOLDER + "empty.xml"); + private static final File MISSING_FILE = new File(TEST_DATA_FOLDER + "missing.xml"); + private static final File VALID_FILE = new File(TEST_DATA_FOLDER + "validAddressBook.xml"); + private static final File MISSING_PERSON_FIELD_FILE = new File(TEST_DATA_FOLDER + "missingPersonField.xml"); + private static final File INVALID_PERSON_FIELD_FILE = new File(TEST_DATA_FOLDER + "invalidPersonField.xml"); + private static final File VALID_PERSON_FILE = new File(TEST_DATA_FOLDER + "validPerson.xml"); + private static final File MISSING_TASK_FIELD_FILE = new File(TEST_DATA_FOLDER + "missingTaskField.xml"); + private static final File INVALID_TASK_FIELD_FILE = new File(TEST_DATA_FOLDER + "invalidTaskField.xml"); + private static final File VALID_TASK_FILE = new File(TEST_DATA_FOLDER + "validTask.xml"); + private static final File TEMP_FILE = new File(TestUtil.getFilePathInSandboxFolder("tempAddressBook.xml")); + + private static final String VALID_DURATION = "3h20m"; + private static final String VALID_DESCRIPTION = "exampleTask1"; + private static final String VALID_DATEANDTIME = "02/03/2018T03:20"; + private static final String INVALID_DATEANDTIME = "2018-00-00T03:20"; +``` +###### \java\seedu\address\commons\util\XmlUtilTest.java +``` java + @Test + public void xmlAdaptedTaskFromFile_fileWithMissingTaskField_validResult() throws Exception { + XmlAdaptedTask actualTask = XmlUtil.getDataFromFile( + MISSING_TASK_FIELD_FILE, XmlAdaptedTaskWithRootElement.class); + XmlAdaptedTask expectedTask = new XmlAdaptedTask( + "exampleTask1", "3:20", null); + assertEquals(expectedTask, actualTask); + } +``` +###### \java\seedu\address\commons\util\XmlUtilTest.java +``` java + @XmlRootElement(name = "tasks") + private static class XmlAdaptedTaskWithRootElement extends XmlAdaptedTask {} +``` +###### \java\seedu\address\logic\commands\ListTaskCommandTest.java +``` java +public class ListTaskCommandTest { + + private Model model; + private Model expectedModel; + private ListTaskCommand listTaskCommand; + + @Before + public void setUp() { + + model = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs()); + + listTaskCommand = new ListTaskCommand(); + listTaskCommand.setData(model, new CommandHistory(), new UndoRedoStack()); + } + + @Test + public void execute_listIsNotFiltered_showsSameList() { + assertCommandSuccess(listTaskCommand, model, ListTaskCommand.MESSAGE_SUCCESS, expectedModel); + } + + @Test + public void execute_listIsFiltered_showsEverything() { + showTaskAtIndex(model, INDEX_FIRST_PERSON); + assertCommandSuccess(listTaskCommand, model, ListTaskCommand.MESSAGE_SUCCESS, expectedModel); + } +} +``` +###### \java\seedu\address\model\AddressBookTest.java +``` java + @Test + public void resetData_withDuplicateTasks_throwsAssertionError() { + // Repeat EXAMPLE1 twice + List newPersons = Arrays.asList(ALICE); + List newTags = new ArrayList<>(ALICE.getTags()); + List newTasks = Arrays.asList(EXAMPLE1, EXAMPLE1); + AddressBookStub newData = new AddressBookStub(newPersons, newTags, newTasks); + + thrown.expect(AssertionError.class); + addressBook.resetData(newData); + } +``` +###### \java\seedu\address\model\AddressBookTest.java +``` java + @Test + public void getTaskList_modifyList_throwsUnsupportedOperationException() { + thrown.expect(UnsupportedOperationException.class); + addressBook.getTaskList().remove(0); + } +``` +###### \java\seedu\address\model\TaskContainsKeywordsPredicateTest.java +``` java +public class TaskContainsKeywordsPredicateTest { + + @Test + public void equals() { + List firstPredicateKeywordList = Collections.singletonList("first"); + List secondPredicateKeywordList = Arrays.asList("first", "second"); + + TaskContainsKeywordsPredicate firstPredicate = new TaskContainsKeywordsPredicate(firstPredicateKeywordList); + TaskContainsKeywordsPredicate secondPredicate = new TaskContainsKeywordsPredicate(secondPredicateKeywordList); + + // same object -> returns true + assertTrue(firstPredicate.equals(firstPredicate)); + + // same values -> returns true + TaskContainsKeywordsPredicate firstPredicateCopy = new TaskContainsKeywordsPredicate(firstPredicateKeywordList); + assertTrue(firstPredicate.equals(firstPredicateCopy)); + + // different types -> returns false + assertFalse(firstPredicate.equals(1)); + + // null -> returns false + assertFalse(firstPredicate.equals(null)); + + + // different person -> returns false + assertFalse(firstPredicate.equals(secondPredicate)); + } + + @Test + public void test_taskContainsKeywords_returnsTrue() { + // One keyword + TaskContainsKeywordsPredicate predicate = new TaskContainsKeywordsPredicate(Collections + .singletonList("exampleTask1")); + assertTrue(predicate.test(new TaskBuilder().withDescription("exampleTask1").buildPersonalTask())); + + // Multiple keywords + predicate = new TaskContainsKeywordsPredicate(Arrays.asList("exampleTask1", "exampleTask2")); + assertTrue(predicate.test(new TaskBuilder().withDescription("exampleTask1 exampleTask2").buildPersonalTask())); + + // Only one matching keyword + predicate = new TaskContainsKeywordsPredicate(Arrays.asList("exampleTask2", "exampleTask3")); + assertTrue(predicate.test(new TaskBuilder().withDescription("exampleTask1 exampleTask3").buildPersonalTask())); + + // Mixed-case keywords + predicate = new TaskContainsKeywordsPredicate(Arrays.asList("eXampleTask1", "ExampleTask2")); + assertTrue(predicate.test(new TaskBuilder().withDescription("exampleTask1 exampleTask2").buildPersonalTask())); + } +} + + +``` +###### \java\seedu\address\storage\XmlAdaptedTaskTest.java +``` java +public class XmlAdaptedTaskTest { + private static final String VALID_DESCRIPTION = "A description"; + + private static final String VALID_DURATION = EXAMPLE1.getDuration(); + private static final String VALID_DATEANDTIME = EXAMPLE1.getTaskDateTime().toString(); //Double check this + + @Test + public void toModelType_validTaskDetails_returnsTask() throws Exception { + XmlAdaptedTask task = new XmlAdaptedTask(EXAMPLE1); + assertEquals(EXAMPLE1, task.toModelType()); + } + + @Test + public void toModelType_nullDescription_throwsIllegalValueException() { + XmlAdaptedTask task = new XmlAdaptedTask(null, VALID_DURATION, VALID_DATEANDTIME); + String expectedMessage = "Task's Tasks Should have a non-empty description field is missing!"; + Assert.assertThrows(IllegalValueException.class, expectedMessage, task::toModelType); + } + + @Test + public void toModelType_nullDuration_throwsIllegalValueException() { + XmlAdaptedTask task = new XmlAdaptedTask(VALID_DESCRIPTION, null, VALID_DATEANDTIME); + String expectedMessage = "Task's Duration must be a non-null value field is missing!"; + Assert.assertThrows(IllegalValueException.class, expectedMessage, task::toModelType); + } + + @Test + public void toModelType_nullDateAndTime_throwsNullPointerException() { + XmlAdaptedTask task = new XmlAdaptedTask(VALID_DESCRIPTION, VALID_DURATION, null); + String expectedMessage = "text"; + Assert.assertThrows(NullPointerException.class, expectedMessage, task::toModelType); + } + +} +``` +###### \java\seedu\address\storage\XmlAddressBookStorageTest.java +``` java + @Test + public void readAndSaveAddressBook_allInOrder_taskSuccess() throws Exception { + String filePath = testFolder.getRoot().getPath() + "TempAddressBook.xml"; + AddressBook original = TypicalTasks.getTypicalAddressBook(); + XmlAddressBookStorage xmlAddressBookStorage = new XmlAddressBookStorage(filePath); + + //Save in new file and read back + xmlAddressBookStorage.saveAddressBook(original, filePath); + ReadOnlyAddressBook readBack = xmlAddressBookStorage.readAddressBook(filePath).get(); + assertEquals(original, new AddressBook(readBack)); + + //Modify data, overwrite exiting file, and read back + original.removeTask(EXAMPLE2); + original.addTask(EXAMPLE2); + xmlAddressBookStorage.saveAddressBook(original, filePath); + readBack = xmlAddressBookStorage.readAddressBook(filePath).get(); + assertEquals(original, new AddressBook(readBack)); + + //Save and read without specifying file path + original.addTask(EXAMPLE4); + xmlAddressBookStorage.saveAddressBook(original); //file path not specified + readBack = xmlAddressBookStorage.readAddressBook().get(); //file path not specified + assertEquals(original, new AddressBook(readBack)); + + } + + @Test + public void saveAddressBook_nullAddressBook_throwsNullPointerException() { + thrown.expect(NullPointerException.class); + saveAddressBook(null, "SomeFile.xml"); + } + + /** + * Saves {@code addressBook} at the specified {@code filePath}. + */ + private void saveAddressBook(ReadOnlyAddressBook addressBook, String filePath) { + try { + new XmlAddressBookStorage(filePath).saveAddressBook(addressBook, addToTestDataPathIfNotNull(filePath)); + } catch (IOException ioe) { + throw new AssertionError("There should not be an error writing to the file.", ioe); + } + } + + @Test + public void saveAddressBook_nullFilePath_throwsNullPointerException() throws IOException { + thrown.expect(NullPointerException.class); + saveAddressBook(new AddressBook(), null); + } + +} +``` +###### \java\seedu\address\storage\XmlSerializableAddressBookTest.java +``` java + @Test + public void toModelType_invalidTaskFile_throwsNullValueException() throws Exception { + XmlSerializableAddressBook dataFromFile = XmlUtil.getDataFromFile(INVALID_TASK_FILE, + XmlSerializableAddressBook.class); + thrown.expect(NullPointerException.class); + dataFromFile.toModelType(); + } + @Test + public void toModelType_invalidTagFile_throwsIllegalValueException() throws Exception { + XmlSerializableAddressBook dataFromFile = XmlUtil.getDataFromFile(INVALID_TAG_FILE, + XmlSerializableAddressBook.class); + thrown.expect(IllegalValueException.class); + dataFromFile.toModelType(); + } +} +``` +###### \java\seedu\address\testutil\TaskBuilder.java +``` java +public class TaskBuilder { + + public static final String DEFAULT_TUTEE_NAME = "Alice Pauline"; + public static final String DEFAULT_DATE = "12/12/2016"; + public static final String DEFAULT_TIME = "12:00"; + public static final String DEFAULT_DATE_TIME = DEFAULT_DATE + " " + DEFAULT_TIME; + public static final String DEFAULT_DURATION = "1h30m"; + public static final String DEFAULT_DESCRIPTION = "Alice's homework"; + + private static final String EMPTY_STRING = ""; + + + protected String name; + protected String description; + protected String duration; + protected LocalDateTime dateAndTime; + + private DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd/MM/uuuu HH:mm") + .withResolverStyle(ResolverStyle.STRICT); + + public TaskBuilder() { + name = DEFAULT_TUTEE_NAME; + description = DEFAULT_DESCRIPTION; + duration = DEFAULT_DURATION; + dateAndTime = LocalDateTime.parse(DEFAULT_DATE_TIME, formatter); + } + + /** + * Initializes the TaskBuilder with the data of a given {@code Tuition Task}. + */ + public TaskBuilder(TuitionTask taskToCopy) { + name = taskToCopy.getPerson(); + description = taskToCopy.getDescription(); + duration = taskToCopy.getDuration(); + dateAndTime = taskToCopy.getTaskDateTime(); + } + + /** + * Sets the {@code name} of the {@code Task} that we are building. + */ + public TaskBuilder withTuteeName(String name) { + this.name = name; + return this; + } + + /** + * Sets the {@code description} of the {@code Task} that we are building. + */ + public TaskBuilder withDescription(String description) { + this.description = description; + return this; + } + + /** + * Sets the {@code description} of the {@code Task} that we are building to be empty. + */ + public TaskBuilder withoutDescription() { + this.description = EMPTY_STRING; + return this; + } + + /** + * Sets the {@code duration} of the {@code Task} that we are building. + */ + public TaskBuilder withDuration(String duration) { + this.duration = duration; + return this; + } + + /** + * Sets the {@code DateAndTime} of the {@code Task} that we are building. + */ + + public TaskBuilder withDateTime(String dateAndTime) { + + this.dateAndTime = LocalDateTime.parse(dateAndTime, formatter); + return this; + } + /** + * Ideally, this return variable should be made to a Task class or this function should return + * a tuition task as well + */ + + public PersonalTask buildPersonalTask() { + return new PersonalTask(dateAndTime, duration, description); + } + public TuitionTask buildTuitionTask() { + return new TuitionTask(name, dateAndTime, duration, description); + } +} +``` +###### \java\seedu\address\testutil\TaskUtil.java +``` java +public class TaskUtil { + public static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("dd/MM/uuuu HH:mm") + .withResolverStyle(ResolverStyle.STRICT); + + /** + * Returns an add personal task command string for adding the {@code task}. + */ + public static String getAddPersonalTaskCommand(Task task) { + return AddPersonalTaskCommand.COMMAND_WORD + " " + getPersonalTaskDetails(task); + } + + /** + * Returns the part of command string for the given {@code task}'s details. + */ + public static String getPersonalTaskDetails(Task task) { + StringBuilder sb = new StringBuilder(); + sb.append(task.getStringTaskDateTime() + " "); + sb.append(task.getDuration() + " "); + sb.append(task.getDescription() + " "); + return sb.toString(); + } + +} +``` +###### \java\seedu\address\testutil\TypicalTasks.java +``` java +public class TypicalTasks { + + public static final Task EXAMPLE1 = new TaskBuilder().withTuteeName(null).withDescription("exampleTask1") + .withDuration("3h20m").withDateTime("02/05/2018 03:20").buildPersonalTask(); + public static final Task EXAMPLE2 = new TaskBuilder().withTuteeName(null).withDescription("exampleTask2") + .withDuration("3h20m").withDateTime("02/04/2018 13:20").buildPersonalTask(); + public static final Task EXAMPLE3 = new TaskBuilder().withTuteeName(null).withDescription("exampleTask3") + .withDuration("3h20m").withDateTime("02/06/2018 23:20").buildPersonalTask(); + public static final Task EXAMPLE4 = new TaskBuilder().withTuteeName(null).withDescription("exampleTask4") + .withDuration("3h20m").withDateTime("02/07/2018 23:20").buildPersonalTask(); + + private TypicalTasks() {} // prevents instantiation + /** + * Returns an {@code AddressBook} with all the typical tasks. + */ + public static AddressBook getTypicalAddressBook() { + AddressBook ab = new AddressBook(); + for (Task task : getTypicalTasks()) { + try { + ab.addTask(task); + } catch (TimingClashException e) { + throw new AssertionError("Timing Clash"); + } + } + return ab; + } + + public static List getTypicalTasks() { + return new ArrayList<>(Arrays.asList(EXAMPLE1, EXAMPLE2, EXAMPLE3)); + } + +} +``` diff --git a/collated/test/yungyung04.md b/collated/test/yungyung04.md new file mode 100644 index 000000000000..a7855524e66f --- /dev/null +++ b/collated/test/yungyung04.md @@ -0,0 +1,1676 @@ +# yungyung04 +###### \java\seedu\address\logic\commands\AddPersonalTaskCommandTest.java +``` java +public class AddPersonalTaskCommandTest { + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @Test + public void constructor_nullTask_throwsNullPointerException() { + thrown.expect(NullPointerException.class); + new AddPersonalTaskCommand(null); + } + + @Test + public void execute_taskAcceptedByModel_addSuccessful() throws Exception { + ModelStubAcceptingPersonalTaskAdded modelStub = new ModelStubAcceptingPersonalTaskAdded(); + PersonalTask validTask = new TaskBuilder().buildPersonalTask(); + + CommandResult commandResult = getAddPersonalTaskCommandForTask(validTask, modelStub).execute(); + + assertEquals(String.format(AddPersonalTaskCommand.MESSAGE_SUCCESS, validTask), commandResult.feedbackToUser); + assertEquals(Arrays.asList(validTask), modelStub.tasksAdded); + } + + @Test + public void execute_clashingTask_throwsCommandException() throws Exception { + ModelStub modelStub = new ModelStubThrowingTimingClashException(); + PersonalTask validTask = new TaskBuilder().buildPersonalTask(); + + thrown.expect(CommandException.class); + thrown.expectMessage(MESSAGE_TASK_TIMING_CLASHES); + + getAddPersonalTaskCommandForTask(validTask, modelStub).execute(); + } + + @Test + public void equals() { + PersonalTask firstPersonalTask = new TaskBuilder().withDateTime(VALID_DATE_TIME_AMY).buildPersonalTask(); + PersonalTask secondPersonalTask = new TaskBuilder().withDateTime(VALID_DATE_TIME_BOB).buildPersonalTask(); + + AddPersonalTaskCommand addFirstTask = new AddPersonalTaskCommand(firstPersonalTask); + AddPersonalTaskCommand addFirstTaskCopy = new AddPersonalTaskCommand(firstPersonalTask); + AddPersonalTaskCommand addSecondTask = new AddPersonalTaskCommand(secondPersonalTask); + + LocalDateTime tuitionDateTime = LocalDateTime.parse(VALID_DATE_TIME_AMY, FORMATTER); + AddTuitionTaskCommand addTuitionTask = new AddTuitionTaskCommand( + INDEX_FIRST_PERSON, tuitionDateTime, VALID_DURATION_AMY, VALID_TASK_DESC_AMY); + + // same object -> returns true + assertTrue(addFirstTask.equals(addFirstTask)); + + // same values -> returns true + assertTrue(addFirstTask.equals(addFirstTaskCopy)); + + // different types -> returns false + assertFalse(addFirstTask.equals(1)); + + // null -> returns false + assertFalse(addFirstTask.equals(null)); + + // different task type -> returns false + assertFalse(addFirstTask.equals(addTuitionTask)); + + // different detail -> returns false + assertFalse(addFirstTask.equals(addSecondTask)); + } + + /** + * Generates a new AddPersonalTaskCommand with the details of the given personal task. + */ + private AddPersonalTaskCommand getAddPersonalTaskCommandForTask(PersonalTask task, Model model) { + AddPersonalTaskCommand command = new AddPersonalTaskCommand(task); + command.setData(model, new CommandHistory(), new UndoRedoStack()); + return command; + } + + /** + * A Model stub that always throw a TimingClashException when trying to add a task. + */ + private class ModelStubThrowingTimingClashException extends ModelStub { + @Override + public void addTask(Task task) throws TimingClashException { + throw new TimingClashException(); + } + + + + @Override + public ReadOnlyAddressBook getAddressBook() { + return new AddressBook(); + } + } + + /** + * A Model stub that always accept the task being added. + */ + private class ModelStubAcceptingPersonalTaskAdded extends ModelStub { + final ArrayList tasksAdded = new ArrayList<>(); + + @Override + public void addTask(Task task) throws TimingClashException { + requireNonNull(task); + tasksAdded.add(task); + } + + @Override + public ReadOnlyAddressBook getAddressBook() { + return new AddressBook(); + } + } +} +``` +###### \java\seedu\address\logic\commands\AddTuitionTaskCommandTest.java +``` java +public class AddTuitionTaskCommandTest { + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + private Model model = new ModelManager(getTypicalAddressBook2(), new UserPrefs()); + private LocalDateTime taskDateTimeAmy = LocalDateTime.parse(VALID_DATE_TIME_AMY, FORMATTER); + + @Test + public void constructor_nullTaskDetail_throwsNullPointerException() { + //one of the other 3 task details is null. + thrown.expect(NullPointerException.class); + new AddTuitionTaskCommand(INDEX_FIRST_PERSON, taskDateTimeAmy, VALID_DURATION_AMY, null); + } + + @Test + public void execute_taskAcceptedByModel_addSuccessful() throws Exception { + Model expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs()); + + AddTuitionTaskCommand addTuitionAmy = getAddTuitionTaskCommandForTask( + INDEX_THIRD_PERSON, taskDateTimeAmy, VALID_DURATION_AMY, VALID_TASK_DESC_AMY); + + String expectedMessage = String.format(AddTuitionTaskCommand.MESSAGE_SUCCESS, TASK_AMY); + expectedModel.addTask(TASK_AMY); + + assertCommandSuccess(addTuitionAmy, model, expectedMessage, expectedModel); + } + + @Test + public void execute_invalidIndex_throwsCommandException() throws Exception { + Index outOfBoundIndex = Index.fromOneBased(model.getFilteredTaskList().size() + 1); + AddTuitionTaskCommand command = getAddTuitionTaskCommandForTask(outOfBoundIndex, taskDateTimeAmy, + VALID_DURATION_AMY, VALID_TASK_DESC_AMY); + assertCommandFailure(command, model, Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + } + + @Test + public void execute_clashingTask_throwsCommandException() throws Exception { + thrown.expect(CommandException.class); + thrown.expectMessage(MESSAGE_TASK_TIMING_CLASHES); + + getAddTuitionTaskCommandForTask(INDEX_THIRD_PERSON, taskDateTimeAmy, VALID_DURATION_AMY, + VALID_TASK_DESC_AMY).execute(); + + getAddTuitionTaskCommandForTask(INDEX_FIRST_PERSON, taskDateTimeAmy, VALID_DURATION_AMY, + VALID_TASK_DESC_AMY).execute(); + } + + + @Test + public void equals() { + LocalDateTime taskDateTimeBob = LocalDateTime.parse(VALID_DATE_TIME_BOB, FORMATTER); + + AddTuitionTaskCommand addTuitionAmy = getAddTuitionTaskCommandForTask( + INDEX_THIRD_PERSON, taskDateTimeAmy, VALID_DURATION_AMY, VALID_TASK_DESC_AMY); + AddTuitionTaskCommand addTuitionAmyCopy = getAddTuitionTaskCommandForTask( + INDEX_THIRD_PERSON, taskDateTimeAmy, VALID_DURATION_AMY, VALID_TASK_DESC_AMY); + AddTuitionTaskCommand addTuitionBob = new AddTuitionTaskCommand( + INDEX_SECOND_PERSON, taskDateTimeBob, VALID_DURATION_BOB, VALID_TASK_DESC_BOB); + + // an AddPersonalTaskCommand object with same task details as addTuitionAmy + AddPersonalTaskCommand addPersonalTask = + new AddPersonalTaskCommand(new TaskBuilder(TASK_AMY).buildPersonalTask()); + + // same value -> returns true + assertTrue(addTuitionAmy.equals(addTuitionAmyCopy)); + + // same object -> returns true + assertTrue(addTuitionAmy.equals(addTuitionAmy)); + + // different types -> returns false + assertFalse(addTuitionAmy.equals(1)); + + // null -> returns false + assertFalse(addTuitionAmy.equals(null)); + + // different task type -> returns false + assertFalse(addTuitionAmy.equals(addPersonalTask)); + + // different detail -> returns false + assertFalse(addTuitionAmy.equals(addTuitionBob)); + } + + /** + * Generates a new AddTuitionTaskCommand with the details of the given tuition task. + */ + private AddTuitionTaskCommand getAddTuitionTaskCommandForTask(Index tuteeIndex, LocalDateTime taskDateTime, + String duration, String description) { + AddTuitionTaskCommand command = new AddTuitionTaskCommand(tuteeIndex, taskDateTime, duration, description); + command.setData(model, new CommandHistory(), new UndoRedoStack()); + return command; + } +} +``` +###### \java\seedu\address\logic\commands\DeleteTaskCommandTest.java +``` java +/** + * Contains integration tests (interaction with the Model, UndoCommand and RedoCommand) and unit tests for + * {@code DeleteTaskCommand}. + */ +public class DeleteTaskCommandTest { + + private Model model = new ModelManager(getTypicalAddressBook1(), new UserPrefs()); + + @Test + public void execute_validIndexUnfilteredList_success() throws Exception { + Task taskToDelete = model.getFilteredTaskList().get(INDEX_FIRST_TASK.getZeroBased()); + DeleteTaskCommand deleteTaskCommand = prepareCommand(INDEX_FIRST_TASK); + + String expectedMessage = String.format(DeleteTaskCommand.MESSAGE_SUCCESS, taskToDelete); + + ModelManager expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs()); + expectedModel.deleteTask(taskToDelete); + + assertCommandSuccess(deleteTaskCommand, model, expectedMessage, expectedModel); + } + + @Test + public void execute_invalidIndexUnfilteredList_throwsCommandException() throws Exception { + Index outOfBoundIndex = Index.fromOneBased(model.getFilteredTaskList().size() + 1); + DeleteTaskCommand deleteTaskCommand = prepareCommand(outOfBoundIndex); + + assertCommandFailure(deleteTaskCommand, model, Messages.MESSAGE_INVALID_TASK_DISPLAYED_INDEX); + } + + @Test + public void execute_validIndexFilteredList_success() throws Exception { + showTaskAtIndex(model, INDEX_FIRST_TASK); + + Task taskToDelete = model.getFilteredTaskList().get(INDEX_FIRST_TASK.getZeroBased()); + DeleteTaskCommand deleteTaskCommand = prepareCommand(INDEX_FIRST_TASK); + + String expectedMessage = String.format(DeleteTaskCommand.MESSAGE_SUCCESS, taskToDelete); + + Model expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs()); + expectedModel.deleteTask(taskToDelete); + showNoTask(expectedModel); + + assertCommandSuccess(deleteTaskCommand, model, expectedMessage, expectedModel); + } + + @Test + public void execute_invalidIndexFilteredList_throwsCommandException() { + showTaskAtIndex(model, INDEX_FIRST_TASK); + + Index outOfBoundIndex = INDEX_SECOND_TASK; + // ensures that outOfBoundIndex is still in bounds of address book list + assertTrue(outOfBoundIndex.getZeroBased() < model.getAddressBook().getTaskList().size()); + + DeleteTaskCommand deleteTaskCommand = prepareCommand(outOfBoundIndex); + + assertCommandFailure(deleteTaskCommand, model, Messages.MESSAGE_INVALID_TASK_DISPLAYED_INDEX); + } + + @Test + public void executeUndoRedo_validIndexUnfilteredList_success() throws Exception { + UndoRedoStack undoRedoStack = new UndoRedoStack(); + UndoCommand undoCommand = prepareUndoCommand(model, undoRedoStack); + RedoCommand redoCommand = prepareRedoCommand(model, undoRedoStack); + Task taskToDelete = model.getFilteredTaskList().get(INDEX_FIRST_TASK.getZeroBased()); + DeleteTaskCommand deleteTaskCommand = prepareCommand(INDEX_FIRST_TASK); + Model expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs()); + + // delete -> first task deleted + deleteTaskCommand.execute(); + undoRedoStack.push(deleteTaskCommand); + + // undo -> reverts addressbook back to previous state and filtered task list to show all tasks + assertCommandSuccess(undoCommand, model, UndoCommand.MESSAGE_SUCCESS, expectedModel); + + // redo -> same first task deleted again + expectedModel.deleteTask(taskToDelete); + assertCommandSuccess(redoCommand, model, RedoCommand.MESSAGE_SUCCESS, expectedModel); + } + + @Test + public void executeUndoRedo_invalidIndexUnfilteredList_failure() { + UndoRedoStack undoRedoStack = new UndoRedoStack(); + UndoCommand undoCommand = prepareUndoCommand(model, undoRedoStack); + RedoCommand redoCommand = prepareRedoCommand(model, undoRedoStack); + Index outOfBoundIndex = Index.fromOneBased(model.getFilteredTaskList().size() + 1); + DeleteTaskCommand deleteTaskCommand = prepareCommand(outOfBoundIndex); + + // execution failed -> deleteTaskCommand not pushed into undoRedoStack + assertCommandFailure(deleteTaskCommand, model, Messages.MESSAGE_INVALID_TASK_DISPLAYED_INDEX); + + // no commands in undoRedoStack -> undoCommand and redoCommand fail + assertCommandFailure(undoCommand, model, UndoCommand.MESSAGE_FAILURE); + assertCommandFailure(redoCommand, model, RedoCommand.MESSAGE_FAILURE); + } + + /** + * 1. Deletes a {@code task} from a filtered list. + * 2. Undo the deletion. + * 3. The unfiltered list should be shown now. Verify that the index of the previously deleted task in the + * unfiltered list is different from the index at the filtered list. + * 4. Redo the deletion. This ensures {@code RedoCommand} deletes the task object regardless of indexing. + */ + @Test + public void executeUndoRedo_validIndexFilteredList_sameTaskDeleted() throws Exception { + UndoRedoStack undoRedoStack = new UndoRedoStack(); + UndoCommand undoCommand = prepareUndoCommand(model, undoRedoStack); + RedoCommand redoCommand = prepareRedoCommand(model, undoRedoStack); + DeleteTaskCommand deleteTaskCommand = prepareCommand(INDEX_FIRST_TASK); + Model expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs()); + + showTaskAtIndex(model, INDEX_SECOND_TASK); + Task taskToDelete = model.getFilteredTaskList().get(INDEX_FIRST_TASK.getZeroBased()); + // delete -> deletes second task in unfiltered task list / first task in filtered task list + deleteTaskCommand.execute(); + undoRedoStack.push(deleteTaskCommand); + + // undo -> reverts addressbook back to previous state and filtered task list to show all tasks + assertCommandSuccess(undoCommand, model, UndoCommand.MESSAGE_SUCCESS, expectedModel); + + expectedModel.deleteTask(taskToDelete); + assertNotEquals(taskToDelete, model.getFilteredTaskList().get(INDEX_FIRST_TASK.getZeroBased())); + // redo -> deletes same second task in unfiltered task list + assertCommandSuccess(redoCommand, model, RedoCommand.MESSAGE_SUCCESS, expectedModel); + } + + @Test + public void equals() throws Exception { + DeleteTaskCommand deleteFirstCommand = prepareCommand(INDEX_FIRST_TASK); + DeleteTaskCommand deleteSecondCommand = prepareCommand(INDEX_SECOND_TASK); + + // same object -> returns true + assertTrue(deleteFirstCommand.equals(deleteFirstCommand)); + + // same values -> returns true + DeleteTaskCommand deleteFirstCommandCopy = prepareCommand(INDEX_FIRST_TASK); + assertTrue(deleteFirstCommand.equals(deleteFirstCommandCopy)); + + // one command preprocessed when previously equal -> returns false + deleteFirstCommandCopy.preprocessUndoableCommand(); + assertFalse(deleteFirstCommand.equals(deleteFirstCommandCopy)); + + // different types -> returns false + assertFalse(deleteFirstCommand.equals(1)); + + // null -> returns false + assertFalse(deleteFirstCommand.equals(null)); + + // different person -> returns false + assertFalse(deleteFirstCommand.equals(deleteSecondCommand)); + } + + /** + * Returns a {@code DeleteTaskCommand} with the parameter {@code index}. + */ + private DeleteTaskCommand prepareCommand(Index index) { + DeleteTaskCommand deleteTaskCommand = new DeleteTaskCommand(index); + deleteTaskCommand.setData(model, new CommandHistory(), new UndoRedoStack()); + return deleteTaskCommand; + } + + /** + * Updates {@code model}'s filtered tasks list to show no one. + */ + private void showNoTask(Model model) { + model.updateFilteredTaskList(t -> false); + + assertTrue(model.getFilteredTaskList().isEmpty()); + } +} +``` +###### \java\seedu\address\logic\commands\FindPersonCommandTest.java +``` java +/** + * Contains integration tests (interaction with the Model) for {@code FindPersonCommand}. + */ +public class FindPersonCommandTest { + private static final int INDEX_FIRST_ELEMENT = 0; + private static final int INDEX_SECOND_ELEMENT = 1; + + private Model model = new ModelManager(getTypicalAddressBook2(), new UserPrefs()); + + private final String[] firstNameKeywords = {VALID_NAME_BOB.split("\\s+")[INDEX_FIRST_ELEMENT], + VALID_NAME_AMY.split("\\s+")[INDEX_SECOND_ELEMENT]}; + private final String[] secondNameKeywords = {VALID_NAME_BOB.split("\\s+")[INDEX_FIRST_ELEMENT]}; + + private final FindPersonCommand findFirstName = new FindPersonCommand(CATEGORY_NAME, firstNameKeywords); + private final FindPersonCommand findSecondName = new FindPersonCommand(CATEGORY_NAME, secondNameKeywords); + + @Test + public void equals() { + // same object -> returns true + assertTrue(findFirstName.equals(findFirstName)); + + // same values -> returns true + FindPersonCommand findFirstCommandCopy = new FindPersonCommand(CATEGORY_NAME, firstNameKeywords); + assertTrue(findFirstName.equals(findFirstCommandCopy)); + + // different types -> returns false + assertFalse(findFirstName.equals(1)); + + // null -> returns false + assertFalse(findFirstName.equals(null)); + + // different person -> returns false + assertFalse(findFirstName.equals(findSecondName)); + } + + @Test + public void execute_findName_foundSuccessfully() { + //multiple keywords + findFirstName.setData(model, new CommandHistory(), new UndoRedoStack()); + String expectedMessage = String.format(FindPersonCommand.MESSAGE_SUCCESS + "\n" + + MESSAGE_PERSONS_LISTED_OVERVIEW, 2); + assertCommandSuccess(findFirstName, expectedMessage, Arrays.asList(AMYTUTEE, BOBTUTEE)); + + //single keyword + findSecondName.setData(model, new CommandHistory(), new UndoRedoStack()); + expectedMessage = String.format(FindPersonCommand.MESSAGE_SUCCESS + "\n" + MESSAGE_PERSONS_LISTED_OVERVIEW, 1); + assertCommandSuccess(findSecondName, expectedMessage, Arrays.asList(BOBTUTEE)); + } + + @Test + public void execute_findEducatonLevel_foundSuccessfully() { + String[] educationLevelKeywords = {VALID_EDUCATION_LEVEL_AMY}; + FindPersonCommand findEducationLevel = new FindPersonCommand(CATEGORY_EDUCATION_LEVEL, educationLevelKeywords); + findEducationLevel.setData(model, new CommandHistory(), new UndoRedoStack()); + String expectedMessage = String.format(FindPersonCommand.MESSAGE_SUCCESS + "\n" + + MESSAGE_PERSONS_LISTED_OVERVIEW, 2); + //Alice and Amy have the same education level + assertCommandSuccess(findEducationLevel, expectedMessage, Arrays.asList(ALICETUTEE, AMYTUTEE)); + } + + @Test + public void execute_findGrade_foundSuccessfully() { + String[] gradeKeywords = {VALID_GRADE_AMY, VALID_GRADE_BOB}; + FindPersonCommand findGrade = new FindPersonCommand(CATEGORY_GRADE, gradeKeywords); + findGrade.setData(model, new CommandHistory(), new UndoRedoStack()); + String expectedMessage = String.format(FindPersonCommand.MESSAGE_SUCCESS + "\n" + + MESSAGE_PERSONS_LISTED_OVERVIEW, 2); + assertCommandSuccess(findGrade, expectedMessage, Arrays.asList(AMYTUTEE, BOBTUTEE)); + } + + @Test + public void execute_findSchool_foundSuccessfully() { + String[] schoolKeywords = {VALID_SCHOOL_AMY.split("\\s+")[INDEX_FIRST_ELEMENT]}; + FindPersonCommand findSchool = new FindPersonCommand(CATEGORY_SCHOOL, schoolKeywords); + findSchool.setData(model, new CommandHistory(), new UndoRedoStack()); + String expectedMessage = String.format(FindPersonCommand.MESSAGE_SUCCESS + "\n" + + MESSAGE_PERSONS_LISTED_OVERVIEW, 1); + assertCommandSuccess(findSchool, expectedMessage, Arrays.asList(AMYTUTEE)); + } + + @Test + public void execute_findSubject_foundSuccessfully() { + String[] subjectKeywords = {VALID_SUBJECT_BOB}; + FindPersonCommand findSubject = new FindPersonCommand(CATEGORY_SUBJECT, subjectKeywords); + findSubject.setData(model, new CommandHistory(), new UndoRedoStack()); + String expectedMessage = String.format(FindPersonCommand.MESSAGE_SUCCESS + "\n" + + MESSAGE_PERSONS_LISTED_OVERVIEW, 2); + //Alice and Bob learn the same subject. + assertCommandSuccess(findSubject, expectedMessage, Arrays.asList(ALICETUTEE, BOBTUTEE)); + } + + /** + * Asserts that {@code command} is successfully executed, and
+ * - the command feedback is equal to {@code expectedMessage}
+ * - the {@code FilteredList} is equal to {@code expectedList}
+ * - the {@code AddressBook} in model remains the same after executing the {@code command} + */ + private void assertCommandSuccess(FindPersonCommand command, String expectedMessage, List expectedList) { + AddressBook expectedAddressBook = new AddressBook(model.getAddressBook()); + CommandResult commandResult = command.execute(); + + assertEquals(expectedMessage, commandResult.feedbackToUser); + assertEquals(expectedList, model.getFilteredPersonList()); + assertEquals(expectedAddressBook, model.getAddressBook()); + } +} +``` +###### \java\seedu\address\logic\commands\ListTuteeCommandTest.java +``` java +/** + * Contains integration tests (interaction with the Model) and unit tests for ListTuteeCommand. + */ +public class ListTuteeCommandTest { + + private Model model; + private Model expectedModel; + private ListTuteeCommand listTuteeCommand; + + @Before + public void setUp() { + model = new ModelManager(getTypicalAddressBook2(), new UserPrefs()); + expectedModel = setExpectedModel(model); + + listTuteeCommand = new ListTuteeCommand(); + listTuteeCommand.setData(model, new CommandHistory(), new UndoRedoStack()); + } + + @Test + public void execute_tuteeListIsNotFiltered_showsSameList() { + assertCommandSuccess(listTuteeCommand, model, ListTuteeCommand.MESSAGE_SUCCESS, expectedModel); + } + + @Test + public void execute_tuteeListIsFiltered_showsEverything() { + showPersonAtIndex(model, INDEX_FIRST_PERSON); + assertCommandSuccess(listTuteeCommand, model, ListTuteeCommand.MESSAGE_SUCCESS, expectedModel); + } + + /** + * Returns a model that has been filtered to show only tutees + */ + private ModelManager setExpectedModel(Model model) { + ModelManager modelManager = new ModelManager(model.getAddressBook(), new UserPrefs()); + modelManager.updateFilteredPersonList(Model.PREDICATE_SHOW_ALL_TUTEES); + return modelManager; + } + +} +``` +###### \java\seedu\address\logic\commands\SortPersonCommandTest.java +``` java +/** + * Contains integration tests (interaction with the Model) for {@code SortPersonCommand}. + */ +public class SortPersonCommandTest { + + private Model model = new ModelManager(getTypicalAddressBook2(), new UserPrefs()); + + private final SortPersonCommand sortName = new SortPersonCommand(CATEGORY_NAME); + + @Test + public void equals() { + // same object -> returns true + assertTrue(sortName.equals(sortName)); + + // same values -> returns true + SortPersonCommand sortNameCopy = new SortPersonCommand(CATEGORY_NAME); + assertTrue(sortName.equals(sortNameCopy)); + + // different types -> returns false + assertFalse(sortName.equals(1)); + + // null -> returns false + assertFalse(sortName.equals(null)); + + // different category -> returns false + SortPersonCommand sortGrade = new SortPersonCommand(CATEGORY_GRADE); + assertFalse(sortName.equals(sortGrade)); + } + + @Test + public void execute_sortName_sortedSuccessfully() { + sortName.setData(model, new CommandHistory(), new UndoRedoStack()); + String expectedMessage = String.format(SortPersonCommand.MESSAGE_SUCCESS); + assertCommandSuccess(sortName, expectedMessage, + Arrays.asList(ALICETUTEE, AMYTUTEE, BOBTUTEE, DANIEL)); + } + + @Test + public void execute_sortEducatonLevel_sortedSuccessfully() { + SortPersonCommand sortEducationLevel = new SortPersonCommand(CATEGORY_EDUCATION_LEVEL); + sortEducationLevel.setData(model, new CommandHistory(), new UndoRedoStack()); + String expectedMessage = String.format(SortPersonCommand.MESSAGE_SUCCESS); + assertCommandSuccess(sortEducationLevel, expectedMessage, + Arrays.asList(BOBTUTEE, ALICETUTEE, AMYTUTEE, DANIEL)); + } + + @Test + public void execute_sortGrade_sortedSuccessfully() { + SortPersonCommand sortGrade = new SortPersonCommand(CATEGORY_GRADE); + sortGrade.setData(model, new CommandHistory(), new UndoRedoStack()); + String expectedMessage = String.format(SortPersonCommand.MESSAGE_SUCCESS); + assertCommandSuccess(sortGrade, expectedMessage, + Arrays.asList(BOBTUTEE, AMYTUTEE, ALICETUTEE, DANIEL)); + } + + @Test + public void execute_sortSchool_sortedSuccessfully() { + SortPersonCommand sortSchool = new SortPersonCommand(CATEGORY_SCHOOL); + sortSchool.setData(model, new CommandHistory(), new UndoRedoStack()); + String expectedMessage = String.format(SortPersonCommand.MESSAGE_SUCCESS); + assertCommandSuccess(sortSchool, expectedMessage, + Arrays.asList(ALICETUTEE, AMYTUTEE, BOBTUTEE, DANIEL)); + } + + @Test + public void execute_sortSubject_sortedSuccessfully() { + SortPersonCommand sortSubject = new SortPersonCommand(CATEGORY_SUBJECT); + sortSubject.setData(model, new CommandHistory(), new UndoRedoStack()); + String expectedMessage = String.format(SortPersonCommand.MESSAGE_SUCCESS); + assertCommandSuccess(sortSubject, expectedMessage, + Arrays.asList(AMYTUTEE, ALICETUTEE, BOBTUTEE, DANIEL)); + } + + /** + * Asserts that {@code command} is successfully executed, and
+ * - the command feedback is equal to {@code expectedMessage}
+ * - the {@code FilteredList} is equal to {@code expectedList}
+ * - the {@code AddressBook} in model remains the same after executing the {@code command} + */ + private void assertCommandSuccess(SortPersonCommand command, String expectedMessage, List expectedList) { + AddressBook expectedAddressBook = new AddressBook(model.getAddressBook()); + CommandResult commandResult = command.execute(); + + assertEquals(expectedMessage, commandResult.feedbackToUser); + assertEquals(expectedList, model.getFilteredPersonList()); + assertEquals(expectedAddressBook, model.getAddressBook()); + } +} +``` +###### \java\seedu\address\logic\parser\FindPersonCommandParserTest.java +``` java +/** + * Contains tests for {@code FindPersonCommandParser}. + */ +public class FindPersonCommandParserTest { + private static final int INDEX_FIRST_ELEMENT = 0; + public static final String VALID_FIRST_NAME_BOB = VALID_NAME_BOB.toLowerCase().split("\\s+")[INDEX_FIRST_ELEMENT]; + private FindPersonCommandParser parser = new FindPersonCommandParser(); + + private final String[] nameKeywords = {VALID_FIRST_NAME_BOB}; + private final String[] educationLevelKeywords = {VALID_EDUCATION_LEVEL_AMY.toLowerCase()}; + private final String[] gradeKeywords = {VALID_GRADE_AMY.toLowerCase(), VALID_GRADE_BOB.toLowerCase()}; + private final String[] schoolKeywords = VALID_SCHOOL_AMY.toLowerCase().split("\\s+"); + private final String[] subjectKeywords = {VALID_SUBJECT_AMY.toLowerCase(), VALID_SUBJECT_BOB.toLowerCase()}; + + private final String invalidCategory = "age"; + + @Test + public void parse_invalidArg_throwsParseException() { + //empty input + assertParseFailure(parser, " ", + String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindPersonCommand.MESSAGE_USAGE)); + + //not enough arguments + assertParseFailure(parser, CATEGORY_GRADE, + String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindPersonCommand.MESSAGE_USAGE)); + + //invalid category + assertParseFailure(parser, invalidCategory + " " + schoolKeywords[INDEX_FIRST_ELEMENT], + String.format(MESSAGE_INVALID_FILTER_CATEGORY, FindPersonCommand.MESSAGE_USAGE)); + } + + @Test + public void parse_validArgs_returnsFindCommand() { + // filter by name using a single keyword + FindPersonCommand expectedFindName = new FindPersonCommand(CATEGORY_NAME, nameKeywords); + assertParseSuccess(parser, CATEGORY_NAME + " Bob", expectedFindName); + + // filter by education level using a single keyword + FindPersonCommand expectedFindEducatonLevel = + new FindPersonCommand(CATEGORY_EDUCATION_LEVEL, educationLevelKeywords); + assertParseSuccess(parser, + CATEGORY_EDUCATION_LEVEL + " " + VALID_EDUCATION_LEVEL_AMY, expectedFindEducatonLevel); + + // filter by grade using 2 different keywords + FindPersonCommand expectedFindGrade = new FindPersonCommand(CATEGORY_GRADE, gradeKeywords); + assertParseSuccess(parser, CATEGORY_GRADE + " " + VALID_GRADE_AMY + + " " + VALID_GRADE_BOB, expectedFindGrade); + + // filter by school using multiple keywords from a single school + FindPersonCommand expectedFindSchool = new FindPersonCommand(CATEGORY_SCHOOL, schoolKeywords); + assertParseSuccess(parser, CATEGORY_SCHOOL + " " + VALID_SCHOOL_AMY, expectedFindSchool); + + // filter by subject using 2 different keywords + FindPersonCommand expectedFindSubject = new FindPersonCommand(CATEGORY_SUBJECT, subjectKeywords); + assertParseSuccess(parser, CATEGORY_SUBJECT + " " + VALID_SUBJECT_AMY + + " " + VALID_SUBJECT_BOB, expectedFindSubject); + + // multiple whitespaces between keywords + assertParseSuccess(parser, CATEGORY_NAME + " \n\t " + "Bob", expectedFindName); + } +} +``` +###### \java\seedu\address\logic\parser\NaturalLanguageIdentifierTest.java +``` java +public class NaturalLanguageIdentifierTest { + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + private NaturalLanguageIdentifier identifier = NaturalLanguageIdentifier.getInstance(); + + @Test + public void getInstance_firstTimeCalled_returnInstanceOfClass() { + assertTrue(identifier instanceof NaturalLanguageIdentifier); + } + + @Test + public void getInstance_subsequentCalls_returnSameInstance() { + NaturalLanguageIdentifier identifierCopy = NaturalLanguageIdentifier.getInstance(); + assertEquals(identifier, identifierCopy); + } + + @Test + public void getMonthAsString_recognizableInput_returnMonth() { + LocalDateTime current = LocalDateTime.now(); + + //natural languages which refer to current month + assertEquals(current.getMonth().name(), identifier.getMonthAsString(NATURAL_CURRENT_MONTH)); + assertEquals(current.getMonth().name(), identifier.getMonthAsString(NATURAL_NOW)); + + //natural language which refers to last month + assertEquals(current.getMonth().minus(1).name(), identifier.getMonthAsString(NATURAL_LAST_MONTH)); + + //natural language which refers to next month + assertEquals(current.getMonth().plus(1).name(), identifier.getMonthAsString(NATURAL_NEXT_MONTH)); + } + + @Test + public void getMonthAsString_unrecognizableInput_returnInput() { + LocalDateTime current = LocalDateTime.now(); + String unrecognizable = "unrecognizable input"; + assertEquals(unrecognizable, identifier.getMonthAsString(unrecognizable)); + } + + @Test + public void getMonthAsString_nullInput_returnInput() { + LocalDateTime current = LocalDateTime.now(); + String unrecognizable = null; + thrown.expect(NullPointerException.class); + String result = identifier.getMonthAsString(unrecognizable); + } + + @Test + public void mergeTwoWordedNaturalLanguage_emptyString_returnEmptyString() { + String[] userInputs = {}; + String[] expectedResults = {}; + String[] results = identifier.mergeTwoWordedNaturalLanguage(userInputs); + assertArrayEquals(expectedResults, results); + } + + @Test + public void mergeTwoWordedNaturalLanguage_oneRecognizableElement_returnInputtedArray() { + String[] userInputs = {"this"}; + String[] expectedResults = {"this"}; + String[] results = identifier.mergeTwoWordedNaturalLanguage(userInputs); + assertArrayEquals(expectedResults, userInputs); + } + + @Test + public void mergeTwoWordedNaturalLanguage_oneUnrecognizableElement_returnInputtedArray() { + String[] userInputs = {"unrecognizable"}; + String[] expectedResults = {"unrecognizable"}; + String[] results = identifier.mergeTwoWordedNaturalLanguage(userInputs); + assertArrayEquals(expectedResults, results); + } + + @Test + public void mergeTwoWordedNaturalLanguage_multipleElements_returnMergedArray() { + String[] userInputs = {"this", "month", "today", "unrecognized", "last", "month", "unrecognized"}; + String[] expectedResults = {"this month", "today", "unrecognized", "last month", "unrecognized"}; + String[] results = identifier.mergeTwoWordedNaturalLanguage(userInputs); + assertArrayEquals(expectedResults, results); + } +} +``` +###### \java\seedu\address\logic\parser\ParserUtilTest.java +``` java + @Test + public void parseDateTime_invalidInput_throwsDateTimeParseException() { + //null date and time + Assert.assertThrows(NullPointerException.class, () -> ParserUtil.parseDateTime(null)); + + //invalid date in non leap year + Assert.assertThrows(DateTimeParseException.class, () -> ParserUtil + .parseDateTime("29/02/2018 " + VALID_TIME)); + + //invalid date in century year + Assert.assertThrows(DateTimeParseException.class, () -> ParserUtil + .parseDateTime("29/02/1900 " + VALID_TIME)); + + //invalid date in month with 30 days + Assert.assertThrows(DateTimeParseException.class, () -> ParserUtil + .parseDateTime("31/04/2018 " + VALID_TIME)); + + //invalid date in month with 31 days + Assert.assertThrows(DateTimeParseException.class, () -> ParserUtil + .parseDateTime("32/03/2018 " + VALID_TIME)); + + //invalid hour + Assert.assertThrows(DateTimeParseException.class, () -> ParserUtil + .parseDateTime(VALID_DATE + " 25:00")); + + //invalid minute + Assert.assertThrows(DateTimeParseException.class, () -> ParserUtil + .parseDateTime(VALID_DATE + "12:60")); + } + + @Test + public void parseDateTime_validInput_parsedSuccessfully() { + //beginning of the month + LocalDateTime expectedDateTime = LocalDateTime.parse("01/10/2018 " + VALID_TIME, FORMATTER); + assertEquals(expectedDateTime, parseDateTime("01/10/2018 " + VALID_TIME)); + + //leap year + expectedDateTime = LocalDateTime.parse("29/02/2020 " + VALID_TIME, FORMATTER); + assertEquals(expectedDateTime, parseDateTime("29/02/2020 " + VALID_TIME)); + + //month with 30 days + expectedDateTime = LocalDateTime.parse("30/04/2020 " + VALID_TIME, FORMATTER); + assertEquals(expectedDateTime, parseDateTime("30/04/2020 " + VALID_TIME)); + + //month with 31 days + expectedDateTime = LocalDateTime.parse("31/03/2020 " + VALID_TIME, FORMATTER); + assertEquals(expectedDateTime, parseDateTime("31/03/2020 " + VALID_TIME)); + + //valid time at boundary value + expectedDateTime = LocalDateTime.parse(VALID_DATE + " 12:00", FORMATTER); + assertEquals(expectedDateTime, parseDateTime(VALID_DATE + " 12:00")); + } + + @Test + public void parseDuration_invalidInput_throwsDateTimeParseException() { + //null duration + Assert.assertThrows(NullPointerException.class, () -> ParserUtil.parseDuration(null)); + + //invalid duration + Assert.assertThrows(DurationParseException.class, () -> ParserUtil + .parseDuration(INVALID_DURATION)); + } + + @Test + public void parseDuration_validInput_parsedSuccessfully() throws Exception { + String expectedDuration = VALID_DURATION; + assertEquals(expectedDuration, parseDuration(VALID_DURATION)); + } + + @Test + public void parseDescription_noDescriptionWithinInput_returnsEmptyString() { + //user input without description + String[] validInputs = VALID_TASK_WITHOUT_DESCRIPTION.split("\\s+", MAXIMUM_AMOUNT_OF_PARAMETERS); + String expectedDescription = ""; + assertEquals(expectedDescription, ParserUtil.parseDescription(validInputs, MAXIMUM_AMOUNT_OF_PARAMETERS)); + + //user input with description + validInputs = VALID_TASK_WITH_DESCRIPTION.split("\\s+", MAXIMUM_AMOUNT_OF_PARAMETERS); + expectedDescription = VALID_DESCRIPTION; + assertEquals(expectedDescription, ParserUtil.parseDescription(validInputs, MAXIMUM_AMOUNT_OF_PARAMETERS)); + } +} +``` +###### \java\seedu\address\logic\parser\SortPersonCommandParserTest.java +``` java +/** + * Contains tests for {@code SortPersonCommandParser}. + */ +public class SortPersonCommandParserTest { + private SortPersonCommandParser parser = new SortPersonCommandParser(); + + private final String invalidCategory = "age"; + + @Test + public void parse_invalidArg_throwsParseException() { + //empty input + assertParseFailure(parser, " ", + String.format(MESSAGE_INVALID_COMMAND_FORMAT, SortPersonCommand.MESSAGE_USAGE)); + + //too many arguments + assertParseFailure(parser, CATEGORY_GRADE + " " + CATEGORY_EDUCATION_LEVEL, + String.format(MESSAGE_INVALID_COMMAND_FORMAT, SortPersonCommand.MESSAGE_USAGE)); + + //invalid category + assertParseFailure(parser, invalidCategory, + String.format(MESSAGE_INVALID_SORTER_CATEGORY, SortPersonCommand.MESSAGE_USAGE)); + } + + @Test + public void parse_validArgs_returnsFindCommand() { + // sort by name + SortPersonCommand expectedSortName = new SortPersonCommand(CATEGORY_NAME); + assertParseSuccess(parser, CATEGORY_NAME, expectedSortName); + + // sort by education level + SortPersonCommand expectedSortEducatonLevel = new SortPersonCommand(CATEGORY_EDUCATION_LEVEL); + assertParseSuccess(parser, CATEGORY_EDUCATION_LEVEL, expectedSortEducatonLevel); + + // sort by grade + SortPersonCommand expectedSortGrade = new SortPersonCommand(CATEGORY_GRADE); + assertParseSuccess(parser, CATEGORY_GRADE, expectedSortGrade); + + // sort by school + SortPersonCommand expectedSortSchool = new SortPersonCommand(CATEGORY_SCHOOL); + assertParseSuccess(parser, CATEGORY_SCHOOL, expectedSortSchool); + + // sort by subject + SortPersonCommand expectedSortSubject = new SortPersonCommand(CATEGORY_SUBJECT); + assertParseSuccess(parser, CATEGORY_SUBJECT, expectedSortSubject); + + // multiple whitespaces before and after sort category + assertParseSuccess(parser, " \n\t" + CATEGORY_NAME + "\n\t", expectedSortName); + } +} +``` +###### \java\seedu\address\model\person\PersonSortUtilTest.java +``` java +public class PersonSortUtilTest { + private static final String LOWER_ORDER = "a"; + private static final String MIDDLE_ORDER = "b"; + private static final String HIGHER_ORDER = "c"; + private static final String EDUCATION_LEVEL_PRIMARY = "primary"; + private static final String EDUCATION_LEVEL_SECONDARY = "secondary"; + private static final String EDUCATION_LEVEL_JUNIOR_COLLEGE = "junior college"; + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + private Person lowerOrder = new TuteeBuilder().withName(MIDDLE_ORDER).withEducationLevel(EDUCATION_LEVEL_PRIMARY) + .withGrade(MIDDLE_ORDER).withSchool(MIDDLE_ORDER).withSubject(MIDDLE_ORDER).build(); + private Person higherOrder = new TuteeBuilder().withName(HIGHER_ORDER).withEducationLevel(EDUCATION_LEVEL_SECONDARY) + .withGrade(HIGHER_ORDER).withSchool(HIGHER_ORDER).withSubject(HIGHER_ORDER).build(); + private Person versatileOrder; + + @Test + public void getComparator_validNameCategory_compareSuccessfully() { + //all first person's categories have lower lexicographical order + Comparator comparator = getComparator(CATEGORY_NAME); + int expected = lowerOrder.getName().fullName.compareTo(higherOrder.getName().fullName); + assertCompareSuccessfully(comparator, expected, lowerOrder, higherOrder); + + //first person's name has lower lexicographical order and the other categories have equal order + versatileOrder = new TuteeBuilder((Tutee) lowerOrder).withName(HIGHER_ORDER).build(); + expected = lowerOrder.getName().fullName.compareTo(versatileOrder.getName().fullName); + assertCompareSuccessfully(comparator, expected, lowerOrder, versatileOrder); + + //first person's name has lower lexicographical order but the other categories have higher order + versatileOrder = new TuteeBuilder((Tutee) higherOrder).withEducationLevel(EDUCATION_LEVEL_JUNIOR_COLLEGE) + .withGrade(LOWER_ORDER).withSchool(LOWER_ORDER).withSubject(LOWER_ORDER).build(); + expected = lowerOrder.getName().fullName.compareTo(versatileOrder.getName().fullName); + assertCompareSuccessfully(comparator, expected, lowerOrder, versatileOrder); + + //first person's name has equal lexicographical order + expected = lowerOrder.getName().fullName.compareTo(lowerOrder.getName().fullName); + assertCompareSuccessfully(comparator, expected, lowerOrder, lowerOrder); + + //first person's name has higher lexicographical order + expected = higherOrder.getName().fullName.compareTo(lowerOrder.getName().fullName); + assertCompareSuccessfully(comparator, expected, higherOrder, lowerOrder); + } + + @Test + public void getComparator_validEducationLevelCategory_compareSuccessfully() { + //all first person's categories have lower lexicographical order + Comparator comparator = getComparator(CATEGORY_EDUCATION_LEVEL); + int expected = ((Tutee) lowerOrder).getEducationLevel().toString() + .compareTo(((Tutee) higherOrder).getEducationLevel().toString()); + assertCompareSuccessfully(comparator, expected, lowerOrder, higherOrder); + + //first person's education level has lower lexicographical order and the other categories have equal order + versatileOrder = new TuteeBuilder((Tutee) lowerOrder).withEducationLevel(EDUCATION_LEVEL_SECONDARY).build(); + expected = ((Tutee) lowerOrder).getEducationLevel().toString() + .compareTo(((Tutee) versatileOrder).getEducationLevel().toString()); + assertCompareSuccessfully(comparator, expected, lowerOrder, versatileOrder); + + //first person's education level has lower lexicographical order but the other categories have higher order + versatileOrder = new TuteeBuilder((Tutee) higherOrder).withName(LOWER_ORDER) + .withGrade(LOWER_ORDER).withSchool(LOWER_ORDER).withSubject(LOWER_ORDER).build(); + expected = ((Tutee) lowerOrder).getEducationLevel().toString() + .compareTo(((Tutee) versatileOrder).getEducationLevel().toString()); + assertCompareSuccessfully(comparator, expected, lowerOrder, versatileOrder); + + //first person's education level has equal lexicographical order + expected = ((Tutee) lowerOrder).getEducationLevel().toString() + .compareTo(((Tutee) lowerOrder).getEducationLevel().toString()); + assertCompareSuccessfully(comparator, expected, lowerOrder, lowerOrder); + + //first person's education level has higher lexicographical order + expected = ((Tutee) higherOrder).getEducationLevel().toString() + .compareTo(((Tutee) lowerOrder).getEducationLevel().toString()); + assertCompareSuccessfully(comparator, expected, higherOrder, lowerOrder); + } + + @Test + public void getComparator_validGradeCategory_compareSuccessfully() { + //all first person's categories have lower lexicographical order + Comparator comparator = getComparator(CATEGORY_GRADE); + int expected = ((Tutee) lowerOrder).getGrade().toString() + .compareTo(((Tutee) higherOrder).getGrade().toString()); + assertCompareSuccessfully(comparator, expected, lowerOrder, higherOrder); + + //first person's grade has lower lexicographical order and the other categories have equal order + versatileOrder = new TuteeBuilder((Tutee) lowerOrder).withGrade(HIGHER_ORDER).build(); + expected = ((Tutee) lowerOrder).getGrade().toString() + .compareTo(((Tutee) versatileOrder).getGrade().toString()); + assertCompareSuccessfully(comparator, expected, lowerOrder, versatileOrder); + + //first person's grade has lower lexicographical order but the other categories have higher order + versatileOrder = new TuteeBuilder((Tutee) higherOrder).withEducationLevel(EDUCATION_LEVEL_JUNIOR_COLLEGE) + .withName(LOWER_ORDER).withSchool(LOWER_ORDER).withSubject(LOWER_ORDER).build(); + expected = ((Tutee) lowerOrder).getGrade().toString() + .compareTo(((Tutee) versatileOrder).getGrade().toString()); + assertCompareSuccessfully(comparator, expected, lowerOrder, versatileOrder); + + //first person's grade has equal lexicographical order + expected = ((Tutee) lowerOrder).getGrade().toString() + .compareTo(((Tutee) lowerOrder).getGrade().toString()); + assertCompareSuccessfully(comparator, expected, lowerOrder, lowerOrder); + + //first person's grade has higher lexicographical order + expected = ((Tutee) higherOrder).getGrade().toString() + .compareTo(((Tutee) lowerOrder).getGrade().toString()); + assertCompareSuccessfully(comparator, expected, higherOrder, lowerOrder); + } + + @Test + public void getComparator_validSchoolCategory_compareSuccessfully() { + //all first person's categories have lower lexicographical order + Comparator comparator = getComparator(CATEGORY_SCHOOL); + int expected = ((Tutee) lowerOrder).getSchool().toString() + .compareTo(((Tutee) higherOrder).getSchool().toString()); + assertCompareSuccessfully(comparator, expected, lowerOrder, higherOrder); + + //first person's school has lower lexicographical order and the other categories have equal order + versatileOrder = new TuteeBuilder((Tutee) lowerOrder).withSchool(HIGHER_ORDER).build(); + expected = ((Tutee) lowerOrder).getSchool().toString() + .compareTo(((Tutee) versatileOrder).getSchool().toString()); + assertCompareSuccessfully(comparator, expected, lowerOrder, versatileOrder); + + //first person's school has lower lexicographical order but the other categories have higher order + versatileOrder = new TuteeBuilder((Tutee) higherOrder).withEducationLevel(EDUCATION_LEVEL_JUNIOR_COLLEGE) + .withName(LOWER_ORDER).withGrade(LOWER_ORDER).withSubject(LOWER_ORDER).build(); + expected = ((Tutee) lowerOrder).getSchool().toString() + .compareTo(((Tutee) versatileOrder).getSchool().toString()); + assertCompareSuccessfully(comparator, expected, lowerOrder, versatileOrder); + + //first person's school has equal lexicographical order + expected = ((Tutee) lowerOrder).getSchool().toString() + .compareTo(((Tutee) lowerOrder).getSchool().toString()); + assertCompareSuccessfully(comparator, expected, lowerOrder, lowerOrder); + + //first person's school has higher lexicographical order + expected = ((Tutee) higherOrder).getSchool().toString() + .compareTo(((Tutee) lowerOrder).getSchool().toString()); + assertCompareSuccessfully(comparator, expected, higherOrder, lowerOrder); + } + + @Test + public void getComparator_validSubjectCategory_compareSuccessfully() { + //all first person's categories have lower lexicographical order + Comparator comparator = getComparator(CATEGORY_SUBJECT); + int expected = ((Tutee) lowerOrder).getSubject().toString() + .compareTo(((Tutee) higherOrder).getSubject().toString()); + assertCompareSuccessfully(comparator, expected, lowerOrder, higherOrder); + + //first person's subject has lower lexicographical order and the other categories have equal order + versatileOrder = new TuteeBuilder((Tutee) lowerOrder).withSubject(HIGHER_ORDER).build(); + expected = ((Tutee) lowerOrder).getSubject().toString() + .compareTo(((Tutee) versatileOrder).getSubject().toString()); + assertCompareSuccessfully(comparator, expected, lowerOrder, versatileOrder); + + //first person's subject has lower lexicographical order but the other categories have higher order + versatileOrder = new TuteeBuilder((Tutee) higherOrder).withEducationLevel(EDUCATION_LEVEL_JUNIOR_COLLEGE) + .withName(LOWER_ORDER).withGrade(LOWER_ORDER).withSchool(LOWER_ORDER).build(); + expected = ((Tutee) lowerOrder).getSubject().toString() + .compareTo(((Tutee) versatileOrder).getSubject().toString()); + assertCompareSuccessfully(comparator, expected, lowerOrder, versatileOrder); + + //first person's subject has equal lexicographical order + expected = ((Tutee) lowerOrder).getSubject().toString() + .compareTo(((Tutee) lowerOrder).getSubject().toString()); + assertCompareSuccessfully(comparator, expected, lowerOrder, lowerOrder); + + //first person's subject has higher lexicographical order + expected = ((Tutee) higherOrder).getSubject().toString() + .compareTo(((Tutee) lowerOrder).getSubject().toString()); + assertCompareSuccessfully(comparator, expected, higherOrder, lowerOrder); + } + + @Test + public void getComparator_invalidCategory_assertionErrorHappen() { + thrown.expect(AssertionError.class); + Comparator comparator = getComparator("email"); + } + + @Test + public void compareNameLexicographically_validInput_compareSuccessfully() { + lowerOrder = new PersonBuilder().withName("Albert").build(); + higherOrder = new PersonBuilder().withName("Alice").build(); + + //first person has lower lexicographical order + int expected = PersonSortUtil.compareNameLexicographically(lowerOrder, higherOrder); + int actual = lowerOrder.getName().fullName.compareToIgnoreCase(higherOrder.getName().fullName); + assertEquals(expected, actual); + + //first person has higher lexicographical order + expected = PersonSortUtil.compareNameLexicographically(higherOrder, lowerOrder); + actual = higherOrder.getName().fullName.compareToIgnoreCase(lowerOrder.getName().fullName); + assertEquals(expected, actual); + + //both have exactly same name + Person lowerOrderCopy = new PersonBuilder(lowerOrder).build(); + expected = PersonSortUtil.compareNameLexicographically(lowerOrder, lowerOrderCopy); + actual = lowerOrder.getName().fullName.compareToIgnoreCase(lowerOrderCopy.getName().fullName); + assertEquals(expected, actual); + + //both have same name with different cases -> treated as 2 same namess + higherOrder = new PersonBuilder(lowerOrder).withName("ALBERT").build(); + int expectedFromSameName = expected; + int expectedFromDiffName = compareNameLexicographically(lowerOrder, higherOrder); + assertEquals(expectedFromSameName, expectedFromDiffName); + } + + /** + * Checks whether comparator is able to perform the desired comparison. + */ + private void assertCompareSuccessfully(Comparator comparator, int expected, Person first, Person second) { + int actual = comparator.compare(first, second); + assertEquals(expected, actual); + } +} +``` +###### \java\seedu\address\model\tutee\EducationLevelContainsKeywordsPredicateTest.java +``` java +public class EducationLevelContainsKeywordsPredicateTest { + + @Test + public void equals() { + List firstPredicateKeywordList = Collections.singletonList("first"); + List secondPredicateKeywordList = Arrays.asList("first", "second"); + + EducationLevelContainsKeywordsPredicate firstPredicate = + new EducationLevelContainsKeywordsPredicate(firstPredicateKeywordList); + EducationLevelContainsKeywordsPredicate secondPredicate = + new EducationLevelContainsKeywordsPredicate(secondPredicateKeywordList); + + // same object -> returns true + assertTrue(firstPredicate.equals(firstPredicate)); + + // same values -> returns true + EducationLevelContainsKeywordsPredicate firstPredicateCopy = + new EducationLevelContainsKeywordsPredicate(firstPredicateKeywordList); + assertTrue(firstPredicate.equals(firstPredicateCopy)); + + // different types -> returns false + assertFalse(firstPredicate.equals(1)); + + // null -> returns false + assertFalse(firstPredicate.equals(null)); + + // different education levels -> returns false + assertFalse(firstPredicate.equals(secondPredicate)); + } + + @Test + public void test_educationLevelContainsKeywords_returnsTrue() { + // One keyword + EducationLevelContainsKeywordsPredicate predicate = + new EducationLevelContainsKeywordsPredicate(Collections.singletonList(VALID_EDUCATION_LEVEL_AMY)); + assertTrue(predicate.test(new TuteeBuilder().withEducationLevel(VALID_EDUCATION_LEVEL_AMY).build())); + + // Multiple keywords + predicate = new EducationLevelContainsKeywordsPredicate(Arrays + .asList("junior", "college")); + assertTrue(predicate.test(new TuteeBuilder() + .withEducationLevel("junior college").build())); + + // Only one matching keyword + predicate = new EducationLevelContainsKeywordsPredicate(Arrays.asList("junior")); + assertTrue(predicate.test(new TuteeBuilder() + .withEducationLevel("junior college").build())); + + // Mixed-case keywords + predicate = new EducationLevelContainsKeywordsPredicate(Arrays.asList("JuNiOr", "colLEGE")); + assertTrue(predicate.test(new TuteeBuilder().withEducationLevel("junior college").build())); + } + + @Test + public void test_educationLevelDoesNotContainKeywords_returnsFalse() { + // Zero keywords + EducationLevelContainsKeywordsPredicate predicate = + new EducationLevelContainsKeywordsPredicate(Collections.emptyList()); + assertFalse(predicate.test(new TuteeBuilder().withEducationLevel(VALID_EDUCATION_LEVEL_AMY).build())); + + // Non-matching keyword + predicate = new EducationLevelContainsKeywordsPredicate(Arrays.asList(VALID_EDUCATION_LEVEL_ROBERT)); + assertFalse(predicate.test(new TuteeBuilder().withEducationLevel(VALID_EDUCATION_LEVEL_AMY).build())); + + // Keywords match grade, school and subject, but does not match education level + predicate = new EducationLevelContainsKeywordsPredicate(Arrays.asList("school", "B", "mathematics")); + assertFalse(predicate.test(new TuteeBuilder().withEducationLevel(VALID_EDUCATION_LEVEL_AMY).withSchool("school") + .withGrade("B").withSubject("mathematics").build())); + + // Keywords match email and address, but does not match education level + predicate = new EducationLevelContainsKeywordsPredicate(Arrays + .asList("Bob", "alice@email.com", "Main", "Street")); + assertFalse(predicate.test(new TuteeBuilder().withEducationLevel(VALID_EDUCATION_LEVEL_AMY) + .withEmail("alice@email.com").withAddress("Main Street").build())); + + } +} +``` +###### \java\seedu\address\model\tutee\GradeContainsKeywordsPredicateTest.java +``` java +public class GradeContainsKeywordsPredicateTest { + + @Test + public void equals() { + List firstPredicateKeywordList = Collections.singletonList("first"); + List secondPredicateKeywordList = Arrays.asList("first", "second"); + + GradeContainsKeywordsPredicate firstPredicate = + new GradeContainsKeywordsPredicate(firstPredicateKeywordList); + GradeContainsKeywordsPredicate secondPredicate = + new GradeContainsKeywordsPredicate(secondPredicateKeywordList); + + // same object -> returns true + assertTrue(firstPredicate.equals(firstPredicate)); + + // same values -> returns true + GradeContainsKeywordsPredicate firstPredicateCopy = + new GradeContainsKeywordsPredicate(firstPredicateKeywordList); + assertTrue(firstPredicate.equals(firstPredicateCopy)); + + // different types -> returns false + assertFalse(firstPredicate.equals(1)); + + // null -> returns false + assertFalse(firstPredicate.equals(null)); + + // different education levels -> returns false + assertFalse(firstPredicate.equals(secondPredicate)); + } + + @Test + public void test_gradeContainsKeywords_returnsTrue() { + // One keyword + GradeContainsKeywordsPredicate predicate = + new GradeContainsKeywordsPredicate(Collections.singletonList(VALID_GRADE_AMY)); + assertTrue(predicate.test(new TuteeBuilder().withGrade(VALID_GRADE_AMY).build())); + + // Only one matching keyword + predicate = new GradeContainsKeywordsPredicate(Arrays.asList(VALID_GRADE_AMY, VALID_GRADE_BOB)); + assertTrue(predicate.test(new TuteeBuilder() + .withGrade(VALID_GRADE_AMY).build())); + + // Mixed-case keywords + predicate = new GradeContainsKeywordsPredicate(Arrays.asList("a")); + assertTrue(predicate.test(new TuteeBuilder().withGrade("A").build())); + } + + @Test + public void test_gradeDoesNotContainKeywords_returnsFalse() { + // Zero keywords + GradeContainsKeywordsPredicate predicate = + new GradeContainsKeywordsPredicate(Collections.emptyList()); + assertFalse(predicate.test(new TuteeBuilder().withGrade(VALID_GRADE_AMY).build())); + + // Non-matching keyword + predicate = new GradeContainsKeywordsPredicate(Arrays.asList(VALID_GRADE_BOB)); + assertFalse(predicate.test(new TuteeBuilder().withGrade(VALID_GRADE_AMY).build())); + + // Keywords match education level, school and subject, but does not match grade + predicate = new GradeContainsKeywordsPredicate(Arrays.asList("school", "primary", "mathematics")); + assertFalse(predicate.test(new TuteeBuilder().withGrade(VALID_GRADE_AMY).withSchool("school") + .withEducationLevel("primary").withSubject("mathematics").build())); + + // Keywords match email and address, but does not match grade + predicate = new GradeContainsKeywordsPredicate(Arrays + .asList(VALID_GRADE_BOB, "alice@email.com", "Main", "Street")); + assertFalse(predicate.test(new TuteeBuilder().withGrade(VALID_GRADE_AMY) + .withEmail("alice@email.com").withAddress("Main Street").build())); + + } +} +``` +###### \java\seedu\address\model\tutee\SchoolContainsKeywordsPredicateTest.java +``` java +public class SchoolContainsKeywordsPredicateTest { + + @Test + public void equals() { + List firstPredicateKeywordList = Collections.singletonList("first"); + List secondPredicateKeywordList = Arrays.asList("first", "second"); + + SchoolContainsKeywordsPredicate firstPredicate = + new SchoolContainsKeywordsPredicate(firstPredicateKeywordList); + SchoolContainsKeywordsPredicate secondPredicate = + new SchoolContainsKeywordsPredicate(secondPredicateKeywordList); + + // same object -> returns true + assertTrue(firstPredicate.equals(firstPredicate)); + + // same values -> returns true + SchoolContainsKeywordsPredicate firstPredicateCopy = + new SchoolContainsKeywordsPredicate(firstPredicateKeywordList); + assertTrue(firstPredicate.equals(firstPredicateCopy)); + + // different types -> returns false + assertFalse(firstPredicate.equals(1)); + + // null -> returns false + assertFalse(firstPredicate.equals(null)); + + // different schools -> returns false + assertFalse(firstPredicate.equals(secondPredicate)); + } + + @Test + public void test_schoolContainsKeywords_returnsTrue() { + // One keyword + SchoolContainsKeywordsPredicate predicate = + new SchoolContainsKeywordsPredicate(Collections.singletonList("nan")); + assertTrue(predicate.test(new TuteeBuilder().withSchool("nan hua high school").build())); + + // Only one matching keyword + predicate = new SchoolContainsKeywordsPredicate(Arrays.asList("nan", "victoria")); + assertTrue(predicate.test(new TuteeBuilder().withSchool("victoria").build())); + + // Mixed-case keywords + predicate = new SchoolContainsKeywordsPredicate(Arrays.asList("nan")); + assertTrue(predicate.test(new TuteeBuilder().withSchool("NAN").build())); + } + + @Test + public void test_schoolDoesNotContainKeywords_returnsFalse() { + // Zero keywords + SchoolContainsKeywordsPredicate predicate = + new SchoolContainsKeywordsPredicate(Collections.emptyList()); + assertFalse(predicate.test(new TuteeBuilder().withSchool(VALID_SCHOOL_AMY).build())); + + // Non-matching keyword + predicate = new SchoolContainsKeywordsPredicate(Arrays.asList("victoria")); + assertFalse(predicate.test(new TuteeBuilder().withSchool("nan hua high school").build())); + + // Keywords match education level, grade and subject, but does not match school + predicate = new SchoolContainsKeywordsPredicate(Arrays.asList("B", "primary", "mathematics")); + assertFalse(predicate.test(new TuteeBuilder().withSchool(VALID_SCHOOL_AMY).withGrade("B") + .withEducationLevel("primary").withSubject("mathematics").build())); + + // Keywords match email and address, but does not match school + predicate = new SchoolContainsKeywordsPredicate(Arrays + .asList("victoria", "alice@email.com", "Main", "Street")); + assertFalse(predicate.test(new TuteeBuilder().withSchool("nan hua high school") + .withEmail("alice@email.com").withAddress("Main Street").build())); + + } +} +``` +###### \java\seedu\address\model\tutee\SubjectContainsKeywordsPredicateTest.java +``` java +public class SubjectContainsKeywordsPredicateTest { + + @Test + public void equals() { + List firstPredicateKeywordList = Collections.singletonList("first"); + List secondPredicateKeywordList = Arrays.asList("first", "second"); + + SubjectContainsKeywordsPredicate firstPredicate = + new SubjectContainsKeywordsPredicate(firstPredicateKeywordList); + SubjectContainsKeywordsPredicate secondPredicate = + new SubjectContainsKeywordsPredicate(secondPredicateKeywordList); + + // same object -> returns true + assertTrue(firstPredicate.equals(firstPredicate)); + + // same values -> returns true + SubjectContainsKeywordsPredicate firstPredicateCopy = + new SubjectContainsKeywordsPredicate(firstPredicateKeywordList); + assertTrue(firstPredicate.equals(firstPredicateCopy)); + + // different types -> returns false + assertFalse(firstPredicate.equals(1)); + + // null -> returns false + assertFalse(firstPredicate.equals(null)); + + // different subjects -> returns false + assertFalse(firstPredicate.equals(secondPredicate)); + } + + @Test + public void test_subjectContainsKeywords_returnsTrue() { + // One keyword + SubjectContainsKeywordsPredicate predicate = + new SubjectContainsKeywordsPredicate(Collections.singletonList(VALID_SUBJECT_AMY)); + assertTrue(predicate.test(new TuteeBuilder().withSubject(VALID_SUBJECT_AMY).build())); + + // Only one matching keyword + predicate = new SubjectContainsKeywordsPredicate(Arrays.asList(VALID_SUBJECT_AMY, VALID_SUBJECT_BOB)); + assertTrue(predicate.test(new TuteeBuilder().withSubject(VALID_SUBJECT_AMY).build())); + + // Mixed-case keywords + predicate = new SubjectContainsKeywordsPredicate(Arrays.asList("MatheMAtics")); + assertTrue(predicate.test(new TuteeBuilder().withSubject("mathematics").build())); + } + + @Test + public void test_subjectDoesNotContainKeywords_returnsFalse() { + // Zero keywords + SubjectContainsKeywordsPredicate predicate = + new SubjectContainsKeywordsPredicate(Collections.emptyList()); + assertFalse(predicate.test(new TuteeBuilder().withSubject(VALID_SUBJECT_AMY).build())); + + // Non-matching keyword + predicate = new SubjectContainsKeywordsPredicate(Arrays.asList(VALID_SUBJECT_BOB)); + assertFalse(predicate.test(new TuteeBuilder().withSubject(VALID_SUBJECT_AMY).build())); + + // Keywords match education level, grade and school, but does not match subject + predicate = new SubjectContainsKeywordsPredicate(Arrays.asList("B", "primary", "school")); + assertFalse(predicate.test(new TuteeBuilder().withSubject(VALID_SUBJECT_AMY).withGrade("B") + .withEducationLevel("primary").withSchool("school").build())); + + // Keywords match email and address, but does not match subject + predicate = new SubjectContainsKeywordsPredicate(Arrays + .asList(VALID_SUBJECT_BOB, "alice@email.com", "Main", "Street")); + assertFalse(predicate.test(new TuteeBuilder().withSubject(VALID_SUBJECT_AMY) + .withEmail("alice@email.com").withAddress("Main Street").build())); + + } +} +``` +###### \java\seedu\address\storage\XmlAdaptedPersonTest.java +``` java + //=========== Tutee Related Tests ============================================================= + + @Test + public void toModelType_validTuteeDetails_returnsTutee() throws Exception { + XmlAdaptedPerson tutee = new XmlAdaptedPerson(ALICETUTEE); + assertEquals(ALICETUTEE, tutee.toModelType()); + } + + @Test + public void toModelType_invalidTuteeName_throwsIllegalValueException() { + XmlAdaptedPerson person = + new XmlAdaptedPerson(INVALID_NAME, VALID_TUTEE_PHONE, VALID_TUTEE_EMAIL, VALID_TUTEE_ADDRESS, + VALID_TUTEE_SUBJECT, VALID_TUTEE_GRADE, VALID_TUTEE_EDUCATION_LEVEL, + VALID_TUTEE_SCHOOL, VALID_TUTEE_TAGS); + String expectedMessage = Name.MESSAGE_NAME_CONSTRAINTS; + Assert.assertThrows(IllegalValueException.class, expectedMessage, person::toModelType); + } + + @Test + public void toModelType_nullTuteeName_throwsIllegalValueException() { + XmlAdaptedPerson person = + new XmlAdaptedPerson(null, VALID_TUTEE_PHONE, VALID_TUTEE_EMAIL, VALID_TUTEE_ADDRESS, + VALID_TUTEE_SUBJECT, VALID_TUTEE_GRADE, VALID_TUTEE_EDUCATION_LEVEL, + VALID_TUTEE_SCHOOL, VALID_TUTEE_TAGS); + String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, Name.class.getSimpleName()); + Assert.assertThrows(IllegalValueException.class, expectedMessage, person::toModelType); + } + + @Test + public void toModelType_invalidTuteePhone_throwsIllegalValueException() { + XmlAdaptedPerson person = + new XmlAdaptedPerson(VALID_TUTEE_NAME, INVALID_PHONE, VALID_TUTEE_EMAIL, VALID_TUTEE_ADDRESS, + VALID_TUTEE_SUBJECT, VALID_TUTEE_GRADE, VALID_TUTEE_EDUCATION_LEVEL, + VALID_TUTEE_SCHOOL, VALID_TUTEE_TAGS); + String expectedMessage = Phone.MESSAGE_PHONE_CONSTRAINTS; + Assert.assertThrows(IllegalValueException.class, expectedMessage, person::toModelType); + } + + @Test + public void toModelType_nullTuteePhone_throwsIllegalValueException() { + XmlAdaptedPerson person = + new XmlAdaptedPerson(VALID_TUTEE_NAME, null, VALID_TUTEE_EMAIL, VALID_TUTEE_ADDRESS, + VALID_TUTEE_SUBJECT, VALID_TUTEE_GRADE, VALID_TUTEE_EDUCATION_LEVEL, + VALID_TUTEE_SCHOOL, VALID_TUTEE_TAGS); + String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, Phone.class.getSimpleName()); + Assert.assertThrows(IllegalValueException.class, expectedMessage, person::toModelType); + } + + @Test + public void toModelType_invalidTuteeEmail_throwsIllegalValueException() { + XmlAdaptedPerson person = + new XmlAdaptedPerson(VALID_TUTEE_NAME, VALID_TUTEE_PHONE, INVALID_EMAIL, VALID_TUTEE_ADDRESS, + VALID_TUTEE_SUBJECT, VALID_TUTEE_GRADE, VALID_TUTEE_EDUCATION_LEVEL, + VALID_TUTEE_SCHOOL, VALID_TUTEE_TAGS); + String expectedMessage = Email.MESSAGE_EMAIL_CONSTRAINTS; + Assert.assertThrows(IllegalValueException.class, expectedMessage, person::toModelType); + } + + @Test + public void toModelType_nullTuteeEmail_throwsIllegalValueException() { + XmlAdaptedPerson person = + new XmlAdaptedPerson(VALID_TUTEE_NAME, VALID_TUTEE_PHONE, null, VALID_TUTEE_ADDRESS, + VALID_TUTEE_SUBJECT, VALID_TUTEE_GRADE, VALID_TUTEE_EDUCATION_LEVEL, + VALID_TUTEE_SCHOOL, VALID_TUTEE_TAGS); + String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, Email.class.getSimpleName()); + Assert.assertThrows(IllegalValueException.class, expectedMessage, person::toModelType); + } + + @Test + public void toModelType_invalidTuteeAddress_throwsIllegalValueException() { + XmlAdaptedPerson person = + new XmlAdaptedPerson(VALID_TUTEE_NAME, VALID_TUTEE_PHONE, VALID_TUTEE_EMAIL, INVALID_ADDRESS, + VALID_TUTEE_SUBJECT, VALID_TUTEE_GRADE, VALID_TUTEE_EDUCATION_LEVEL, + VALID_TUTEE_SCHOOL, VALID_TUTEE_TAGS); + String expectedMessage = Address.MESSAGE_ADDRESS_CONSTRAINTS; + Assert.assertThrows(IllegalValueException.class, expectedMessage, person::toModelType); + } + + @Test + public void toModelType_nullTuteeAddress_throwsIllegalValueException() { + XmlAdaptedPerson person = + new XmlAdaptedPerson(VALID_TUTEE_NAME, VALID_TUTEE_PHONE, VALID_TUTEE_EMAIL, null, + VALID_TUTEE_SUBJECT, VALID_TUTEE_GRADE, VALID_TUTEE_EDUCATION_LEVEL, + VALID_TUTEE_SCHOOL, VALID_TUTEE_TAGS); + String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, Address.class.getSimpleName()); + Assert.assertThrows(IllegalValueException.class, expectedMessage, person::toModelType); + } + + @Test + public void toModelType_invalidSubject_throwsIllegalValueException() { + XmlAdaptedPerson person = + new XmlAdaptedPerson(VALID_TUTEE_NAME, VALID_TUTEE_PHONE, VALID_TUTEE_EMAIL, VALID_TUTEE_ADDRESS, + INVALID_SUBJECT, VALID_TUTEE_GRADE, VALID_TUTEE_EDUCATION_LEVEL, + VALID_TUTEE_SCHOOL, VALID_TUTEE_TAGS); + String expectedMessage = Subject.MESSAGE_SUBJECT_CONSTRAINTS; + Assert.assertThrows(IllegalValueException.class, expectedMessage, person::toModelType); + } + + @Test + public void toModelType_nullSubject_throwsIllegalValueException() { + XmlAdaptedPerson person = + new XmlAdaptedPerson(VALID_TUTEE_NAME, VALID_TUTEE_PHONE, VALID_TUTEE_EMAIL, VALID_TUTEE_ADDRESS, + null, VALID_TUTEE_GRADE, VALID_TUTEE_EDUCATION_LEVEL, + VALID_TUTEE_SCHOOL, VALID_TUTEE_TAGS); + String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, Subject.class.getSimpleName()); + Assert.assertThrows(IllegalValueException.class, expectedMessage, person::toModelType); + } + + @Test + public void toModelType_invalidGrade_throwsIllegalValueException() { + XmlAdaptedPerson person = + new XmlAdaptedPerson(VALID_TUTEE_NAME, VALID_TUTEE_PHONE, VALID_TUTEE_EMAIL, VALID_TUTEE_ADDRESS, + VALID_TUTEE_SUBJECT, INVALID_GRADE, VALID_TUTEE_EDUCATION_LEVEL, + VALID_TUTEE_SCHOOL, VALID_TUTEE_TAGS); + String expectedMessage = Grade.MESSAGE_GRADE_CONSTRAINTS; + Assert.assertThrows(IllegalValueException.class, expectedMessage, person::toModelType); + } + + @Test + public void toModelType_nullGrade_throwsIllegalValueException() { + XmlAdaptedPerson person = + new XmlAdaptedPerson(VALID_TUTEE_NAME, VALID_TUTEE_PHONE, VALID_TUTEE_EMAIL, VALID_TUTEE_ADDRESS, + VALID_TUTEE_SUBJECT, null, VALID_TUTEE_EDUCATION_LEVEL, + VALID_TUTEE_SCHOOL, VALID_TUTEE_TAGS); + String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, Grade.class.getSimpleName()); + Assert.assertThrows(IllegalValueException.class, expectedMessage, person::toModelType); + } + + @Test + public void toModelType_invalidEducationLevel_throwsIllegalValueException() { + XmlAdaptedPerson person = + new XmlAdaptedPerson(VALID_TUTEE_NAME, VALID_TUTEE_PHONE, VALID_TUTEE_EMAIL, VALID_TUTEE_ADDRESS, + VALID_TUTEE_SUBJECT, VALID_TUTEE_GRADE, INVALID_EDUCATION_LEVEL, + VALID_TUTEE_SCHOOL, VALID_TUTEE_TAGS); + String expectedMessage = EducationLevel.MESSAGE_EDUCATION_LEVEL_CONSTRAINTS; + Assert.assertThrows(IllegalValueException.class, expectedMessage, person::toModelType); + } + + @Test + public void toModelType_nullEducationLevel_throwsIllegalValueException() { + XmlAdaptedPerson person = + new XmlAdaptedPerson(VALID_TUTEE_NAME, VALID_TUTEE_PHONE, VALID_TUTEE_EMAIL, VALID_TUTEE_ADDRESS, + VALID_TUTEE_SUBJECT, VALID_TUTEE_GRADE, null, + VALID_TUTEE_SCHOOL, VALID_TUTEE_TAGS); + String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, EducationLevel.class.getSimpleName()); + Assert.assertThrows(IllegalValueException.class, expectedMessage, person::toModelType); + } + + @Test + public void toModelType_invalidSchool_throwsIllegalValueException() { + XmlAdaptedPerson person = + new XmlAdaptedPerson(VALID_TUTEE_NAME, VALID_TUTEE_PHONE, VALID_TUTEE_EMAIL, VALID_TUTEE_ADDRESS, + VALID_TUTEE_SUBJECT, VALID_TUTEE_GRADE, VALID_TUTEE_EDUCATION_LEVEL, + INVALID_SCHOOL, VALID_TUTEE_TAGS); + String expectedMessage = School.MESSAGE_SCHOOL_CONSTRAINTS; + Assert.assertThrows(IllegalValueException.class, expectedMessage, person::toModelType); + } + + @Test + public void toModelType_nullSchool_throwsIllegalValueException() { + XmlAdaptedPerson person = + new XmlAdaptedPerson(VALID_TUTEE_NAME, VALID_TUTEE_PHONE, VALID_TUTEE_EMAIL, VALID_TUTEE_ADDRESS, + VALID_TUTEE_SUBJECT, VALID_TUTEE_GRADE, VALID_TUTEE_EDUCATION_LEVEL, + null, VALID_TUTEE_TAGS); + String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, School.class.getSimpleName()); + Assert.assertThrows(IllegalValueException.class, expectedMessage, person::toModelType); + } + + @Test + public void toModelType_tuteeHasInvalidTags_throwsIllegalValueException() { + List invalidTags = new ArrayList<>(VALID_TUTEE_TAGS); + invalidTags.add(new XmlAdaptedTag(INVALID_TAG)); + XmlAdaptedPerson person = + new XmlAdaptedPerson(VALID_TUTEE_NAME, VALID_TUTEE_PHONE, VALID_TUTEE_EMAIL, VALID_TUTEE_ADDRESS, + VALID_TUTEE_SUBJECT, VALID_TUTEE_GRADE, VALID_TUTEE_EDUCATION_LEVEL, + VALID_TUTEE_SCHOOL, invalidTags); + Assert.assertThrows(IllegalValueException.class, person::toModelType); + } +} +``` +###### \java\systemtests\AddressBookSystemTest.java +``` java + /** + * Adds a tutee into the current model and expected model. + */ + protected void addTutee(String command, Tutee tutee, Model expectedModel) { + try { + expectedModel.addPerson(tutee); + } catch (DuplicatePersonException dpe) { + System.out.println("a tutee with the same name exists in the expected model"); + } + executeCommand(command); + } +``` +###### \java\systemtests\FindPersonCommandSystemTest.java +``` java + /* Adding some tutees into the filtered person list to test whether Find Person command can find tutees */ + // adds AMYTUTEE + expectedModel = getModel(); + command = AddTuteeCommand.COMMAND_WORD + " " + NAME_DESC_AMY + " " + PHONE_DESC_AMY + " " + + EMAIL_DESC_AMY + " " + ADDRESS_DESC_AMY + " " + SUBJECT_DESC_AMY + " " + GRADE_DESC_AMY + " " + + EDUCATION_LEVEL_DESC_AMY + " " + SCHOOL_DESC_AMY + " " + TAG_DESC_FRIEND + " "; + addTutee(command, AMYTUTEE, expectedModel); + + //adds Bob whose subject and school are same as Amy's + Tutee modifiedBobTutee = new TuteeBuilder().withName(VALID_NAME_BOB).withPhone(VALID_PHONE_BOB) + .withEmail(VALID_EMAIL_BOB).withAddress(VALID_ADDRESS_BOB).withSubject(VALID_SUBJECT_AMY) + .withGrade(VALID_GRADE_BOB).withEducationLevel(VALID_EDUCATION_LEVEL_BOB).withSchool(VALID_SCHOOL_AMY) + .withTags(VALID_TAG_HUSBAND, VALID_TAG_FRIEND).build(); + command = AddTuteeCommand.COMMAND_WORD + " " + NAME_DESC_BOB + " " + PHONE_DESC_BOB + " " + + EMAIL_DESC_BOB + " " + ADDRESS_DESC_BOB + " " + SUBJECT_DESC_AMY + " " + GRADE_DESC_BOB + " " + + EDUCATION_LEVEL_DESC_BOB + " " + SCHOOL_DESC_AMY + " " + TAG_DESC_HUSBAND + " " + TAG_DESC_FRIEND; + addTutee(command, modifiedBobTutee, expectedModel); + + /* Case: find education level of a tutee in address book -> 1 person found */ + ModelHelper.setFilteredList(expectedModel, AMYTUTEE); + command = FindPersonCommand.COMMAND_WORD + " " + CATEGORY_EDUCATION_LEVEL + " " + + AMYTUTEE.getEducationLevel().toString(); + assertCommandSuccess(command, expectedModel); + assertSelectedCardUnchanged(); + + /* Case: find grade of a tutee using command alias in address book -> 1 person found */ + command = FindPersonCommand.COMMAND_ALIAS + " " + CATEGORY_GRADE + " " + + AMYTUTEE.getGrade().toString(); + assertCommandSuccess(command, expectedModel); + assertSelectedCardUnchanged(); + + /* Case: find school of a tutee in address book -> 2 persons found */ + ModelHelper.setFilteredList(expectedModel, AMYTUTEE, modifiedBobTutee); + command = FindPersonCommand.COMMAND_WORD + " " + CATEGORY_SCHOOL + " " + + AMYTUTEE.getSchool().toString(); + assertCommandSuccess(command, expectedModel); + assertSelectedCardUnchanged(); + + /* Case: find subject of a tutee in address book -> 2 persons found */ + command = FindPersonCommand.COMMAND_WORD + " " + CATEGORY_SUBJECT + " " + + AMYTUTEE.getSubject().toString(); + assertCommandSuccess(command, expectedModel); + assertSelectedCardUnchanged(); +``` +###### \java\systemtests\ModelHelper.java +``` java + /** + * Updates {@code model}'s sorted list to display persons based on specified category. + */ + public static void setSortedList(Model model, String category) { + Comparator comparator = new PersonSortUtil().getComparator(category); + model.sortFilteredPersonList(comparator); + } +``` diff --git a/docs/AboutUs.adoc b/docs/AboutUs.adoc index 0f0a8e7ab51e..ebf8e5dbeb4e 100644 --- a/docs/AboutUs.adoc +++ b/docs/AboutUs.adoc @@ -3,53 +3,44 @@ :imagesDir: images :stylesDir: stylesheets -AddressBook - Level 4 was developed by the https://se-edu.github.io/docs/Team.html[se-edu] team. + -_{The dummy content given below serves as a placeholder to be used by future forks of the project.}_ + -{empty} + +TuitionConnect was developed by the MarvelousBook team. + + We are a team based in the http://www.comp.nus.edu.sg[School of Computing, National University of Singapore]. == Project Team -=== John Doe -image::damithc.jpg[width="150", align="left"] -{empty}[http://www.comp.nus.edu.sg/~damithch[homepage]] [https://github.com/damithc[github]] [<>] - -Role: Project Advisor - -''' - -=== John Roe -image::lejolly.jpg[width="150", align="left"] -{empty}[http://github.com/lejolly[github]] [<>] +=== Eka Buyung Lienadi +image::yungyung04.jpg[width="150", align="left"] +{empty}[http://github.com/yungyung04[github]] [<>] Role: Team Lead + -Responsibilities: UI +Responsibilities: Storage + Scheduling and Tracking ''' -=== Johnny Doe -image::yijinl.jpg[width="150", align="left"] -{empty}[http://github.com/yijinl[github]] [<>] +=== Cho Chih Tun +image::chochihtun.jpg[width="150", align="left"] +{empty}[http://github.com/ChoChihTun[github]] [<>] Role: Developer + -Responsibilities: Data +Responsibilities: UI + Code Quality ''' -=== Johnny Roe -image::m133225.jpg[width="150", align="left"] -{empty}[http://github.com/m133225[github]] [<>] +=== Raymond Zheng +image::raymond511.jpg[width="150", align="left"] +{empty}[http://github.com/raymond511[github]] [<>] Role: Developer + -Responsibilities: Dev Ops + Threading +Responsibilities: Model + Testing ''' -=== Benson Meier -image::yl_coder.jpg[width="150", align="left"] -{empty}[http://github.com/yl-coder[github]] [<>] +=== Anas Shakra +image::a-shakra.jpg[width="150", align="left"] +{empty}[http://github.com/a-shakra[github]] [<>] Role: Developer + -Responsibilities: UI +Responsibilities: Logic + Integration ''' diff --git a/docs/ContactUs.adoc b/docs/ContactUs.adoc index eafdc9574a50..463c6758f0c5 100644 --- a/docs/ContactUs.adoc +++ b/docs/ContactUs.adoc @@ -3,4 +3,4 @@ * *Bug reports, Suggestions* : Post in our https://github.com/se-edu/addressbook-level4/issues[issue tracker] if you noticed bugs or have suggestions on how to improve. * *Contributing* : We welcome pull requests. Follow the process described https://github.com/oss-generic/process[here] -* *Email us* : You can also reach us at `damith [at] comp.nus.edu.sg` +* *Email us* : You can also reach us at `tuitionconnect [at] comp.nus.edu.sg` diff --git a/docs/DeveloperGuide.adoc b/docs/DeveloperGuide.adoc index 1733af113b29..ad4363e6c99a 100644 --- a/docs/DeveloperGuide.adoc +++ b/docs/DeveloperGuide.adoc @@ -1,4 +1,4 @@ -= AddressBook Level 4 - Developer Guide += TuitionConnect (TC) - Developer Guide :toc: :toc-title: :toc-placement: preamble @@ -10,9 +10,9 @@ ifdef::env-github[] :tip-caption: :bulb: :note-caption: :information_source: endif::[] -:repoURL: https://github.com/se-edu/addressbook-level4/tree/master +:repoURL: https://github.com/CS2103JAN2018-T11-B1/main -By: `Team SE-EDU`      Since: `Jun 2016`      Licence: `MIT` +By: `Team MarvelousBook` Since: `Feb 2018` Licence: `MIT` == Setting up @@ -140,7 +140,7 @@ The _Sequence Diagram_ below shows how the components interact for the scenario image::SDforDeletePerson.png[width="800"] [NOTE] -Note how the `Model` simply raises a `AddressBookChangedEvent` when the Address Book data are changed, instead of asking the `Storage` to save the updates to the hard disk. +Note how the `Model` simply raises a `AddressBookChangedEvent` when the <> data are changed, instead of asking the `Storage` to save the updates to the hard disk. The diagram below shows how the `EventsCenter` reacts to that event, which eventually results in the updates being saved to the hard disk and the status bar of the UI being updated to reflect the 'Last Updated' time. @@ -158,7 +158,7 @@ The sections below give more details of each component. .Structure of the UI Component image::UiClassDiagram.png[width="800"] -*API* : link:{repoURL}/src/main/java/seedu/address/ui/Ui.java[`Ui.java`] +*API* : link:{https://github.com/CS2103JAN2018-T11-B1/main}/src/main/java/seedu/address/ui/Ui.java[`Ui.java`] The UI consists of a `MainWindow` that is made up of parts e.g.`CommandBox`, `ResultDisplay`, `PersonListPanel`, `StatusBarFooter`, `BrowserPanel` etc. All these, including the `MainWindow`, inherit from the abstract `UiPart` class. @@ -181,7 +181,7 @@ image::LogicClassDiagram.png[width="800"] image::LogicCommandClassDiagram.png[width="800"] *API* : -link:{repoURL}/src/main/java/seedu/address/logic/Logic.java[`Logic.java`] +link:{https://github.com/CS2103JAN2018-T11-B1/main}/src/main/java/seedu/address/logic/Logic.java[`Logic.java`] . `Logic` uses the `AddressBookParser` class to parse the user command. . This results in a `Command` object which is executed by the `LogicManager`. @@ -193,34 +193,38 @@ Given below is the Sequence Diagram for interactions within the `Logic` componen .Interactions Inside the Logic Component for the `delete 1` Command image::DeletePersonSdForLogic.png[width="800"] +// tag::classDiagram[] [[Design-Model]] + === Model component .Structure of the Model Component -image::ModelClassDiagram.png[width="800"] +image::ModelClassDiagram2.png[width="800"] -*API* : link:{repoURL}/src/main/java/seedu/address/model/Model.java[`Model.java`] +*API* : link:{https://github.com/CS2103JAN2018-T11-B1/main}/src/main/java/seedu/address/model/Model.java[`Model.java`] The `Model`, * stores a `UserPref` object that represents the user's preferences. * stores the Address Book data. -* exposes an unmodifiable `ObservableList` that can be 'observed' e.g. the UI can be bound to this list so that the UI automatically updates when the data in the list change. +* exposes an unmodifiable `ObservableList` and `ObservableList` that can be 'observed' e.g. the UI can be bound to this list so that the UI automatically updates when the data in the list change. * does not depend on any of the other three components. +// end::classDiagram[] +// tag::storageDiagram[] [[Design-Storage]] === Storage component .Structure of the Storage Component -image::StorageClassDiagram.png[width="800"] +image::StorageClassDiagram2.png[width="800"] -*API* : link:{repoURL}/src/main/java/seedu/address/storage/Storage.java[`Storage.java`] +*API* : link:{https://github.com/CS2103JAN2018-T11-B1/main}/src/main/java/seedu/address/storage/Storage.java[`Storage.java`] The `Storage` component, * can save `UserPref` objects in json format and read it back. * can save the Address Book data in xml format and read it back. - +// end::storageDiagram[] [[Design-Commons]] === Common classes @@ -230,6 +234,76 @@ Classes used by multiple components are in the `seedu.addressbook.commons` packa This section describes some noteworthy details on how certain features are implemented. +// tag::addtutee[] +=== Add tutee Command +==== Current Implementation + +===== Reason for Implementation +As the user of this application is a private tutor, he or she will need to add the <> contact details. +Since a tutee contains more details specific to them compared to a person, tutee contacts should not be mixed with the <> contacts so that all the contacts are organized and can be managed easily. +Therefore, AddTuteeCommand is used to add only the <>. + +===== How it is implemented +The AddTuteeCommand is an extension of the Undoable Command, which is part of the Logic Component. +As this command involves the altering of the application's state, the ability to undo and redo will make this command more user-friendly and efficient. +(e.g list command + delete command vs undo command). + +image::LogicCommandClassDiagram.png[width="800"] + +From the diagram shown above, instead of extending abstract Command class directly, AddTuteeCommand will extend the abstract Undoable Command class like clearCommand and AddCommand. +This allows AddTuteeCommand to use or override the state saving codes inside the Undoable Command class as shown below. + +[source,java] +---- +public abstract class UndoableCommand extends Command { + @Override + public CommandResult execute() { + // ... undo logic ... + + executeUndoableCommand(); + } +} + +public class AddTuteeCommand extends UndoableCommand { + @Override + public CommandResult executeUndoableCommand() { + // ... add tutee logic ... + } +} +---- + +The following [underline]#*sequence diagram*# shows how the addtutee operation works: + +image::AddTuteeCommandSequenceDiagram.png[width="800"] + +As shown in the sequence diagram, after Tutee object has been created, it is being passed to Model. Model manager will add the Tutee object to the person list in TuitionConnect. +By having a combined list of tutee and person, we can manage the contacts using one command, such as delete command, instead of having 1 command for tutee and 1 command for person. +The combined list is achieved through the use of inheritance, whereby Tutee class inherits the Person class as shown in the diagram below. + +image:TuteeClassDiagram.jpg[width="600" height="400"] + +Since a tutee is a person, we can carried out inheritance by extending the Person class. +Both Tutee and Person class objects have the same fields such as Name, Phone, Email and Address. +However, Tutee class object has additional details such as Subject, Grade, EducationLevel and School, which are irrelevant to a Person class object. + +[NOTE] +If the person already exists, either as tutee or person, in the contact list, DuplicatePersonException will be thrown. + +The following [underline]#*activity diagram*# shows the workflow when adding a tutee. + +image:AddTuteeActivityDiagram.png[width="800] + +==== Design Consideration + +* **Alternative 1 (current choice):** Create a new command "addtutee" and a tutee class, which extends person class +** Pros: Person will not have any detail that is specific to tutee only. This saves memory especially when there are numerous person objects. +** Cons: A tutee cannot simply convert into a person object when tutor stops teaching him/her. Tutor has to delete the tutee and add him/her again as a person. + +* **Alternative 2:** Modify the current AddCommand and add more fields to the person class +** Pros: Features can be implemented easily without modifying much to the existing code. +** Cons: Person object creates unrelated field objects such as "subject" and "school" and these objects will waste the memory space. This problem will be significant when there are a lot of person objects. +// end::addtutee[] + // tag::undoredo[] === Undo/Redo feature ==== Current Implementation @@ -358,6 +432,53 @@ image::UndoRedoActivityDiagram.png[width="650"] ** Cons: Requires dealing with commands that have already been undone: We must remember to skip these commands. Violates Single Responsibility Principle and Separation of Concerns as `HistoryManager` now needs to do two * different things. // end::undoredo[] +// tag::sortpersoncommand[] +=== Sort Person Command + +==== Reason for Implementation +As the amount of contacts stored grows, it would be harder to browse the address book. Hence, Sort Person Command is created +to ease users's experience in organizing their address book. + +==== How it is implemented +The following sequence diagram shows the general idea of how sort person command works: + +image::SortPersonSequenceDiagram.png[width="800"] + +As shown in the sequence diagram, the Logic command's call for execution results in Sort Person command to request for a Comparator object from PersonSortUtil class. +A comparator object is required since the sorting implementation utilizes JavaFX8's SortedList class. +(Refer to https://docs.oracle.com/javase/8/javafx/api/javafx/collections/transformation/SortedList.html for more information) +Here, PersonSortUtil class is responsible in providing a suitable comparator that matches the sorting behaviour requested by the user. Upon receiving the comparator, SortPersonCommand +calls the sortFilteredPersonList() method which utilises a SortedList object to perform the sorting. +// end::sortpersoncommand[] + +// tag::natural[] +=== Natural Language Identifier + +==== Reason for Implementation +Figuring out the exact date for a tuition appointment can be tedious in some extent. Tutors would need to flip over a calendar, or to perform some +operations in the context of this app. Based on this concern, as our goal is to simplify the life of tutor, +we feel the importance of introducing a feature where tutors are able to set a tuition appointment not only by date, +but also by using common terms such as: `today`, `next month`, etc. + +==== Current Implementation Limitation +In the current implementation, common terms (or Natural Languages) are only identifiable by the `Find Task` command parser. + +==== How it is implemented +A class named NaturalLanguageIdentifier applys the singleton design pattern as the class stores a private attribute that records the current time of the system clock. +As this reference time must be the same throughout the entire execution of a command, this class should only be instantiated once. + +This class is responsible for identifying whether a given user input is a recognizable natural language and convert it +into the desired format when applicable. In achieving this purpose, the public methods within this class are called by +the command parsers that inherit Address Book parser. In general, these public methods have a certain characteristic. +These methods always take in a user's input as parameter and either return the same input if it is not recognized as a valid natural language +or return a processable form of the natural language. For example, given *this month* (a valid natural language) as user input, +the getMonthAsString() method will return *April* (the current month). + +Valid or recognized natural languages are determined by a static list inside the NaturalLanguageIdentifier class. +This list of Strings stores common terms used by tutors. If a given user input matches one of the terms in the list, that input +is treated as a valid natural language. +// end::natural[] + // tag::dataencryption[] === [Proposed] Data Encryption @@ -790,6 +911,80 @@ See this https://github.com/se-edu/addressbook-level4/pull/599[PR] for the step- *Value proposition*: manage contacts faster than a typical mouse/GUI driven app +=== Feature Contribution + +*Chih Tun*: + +* *Major Enhancement*: Integrated a calendar into TuitionConnect using third party software, CalendarFX. +. Revamp UI layout and colour so as to incorporate calendar to the UI +. Implemented change command to display day, week, month or year view of the calendar. A daily agenda list is showed together with the calendar's day view. +. In addition, the calendar reflects any changes to the schedule such as adding of a new task, deleting an existing task or undo. + +* *Minor Enhancement*: +. Created command to add a new tutee. +. Implemented tutee, tuition, personal task classes and relevant field classes to support all the commands. +. Modify edit command to be able to edit tutee's details. + + +*Eka Buyung Lienadi*: + +* *Scheduling features [Major Enhancement]* + +. Implemented task addition, deletion, filtering and sorting features. +. Implemented features to recognize natural language, which aids in task filtering. + +* [Minor Enhancement] +. Implemented tutee listing feature. +. Implemented tutee filtering and sorting features. + +*Anas*: +// tag::taskClass[] +. *Integrating the Task class into the Addressbook* [Major Enhancement] + +The Task class forms the foundation of TuitionConnect as it allows the user to manage their various +"tasks". The task class comes in two forms, a personal task and a tuition task. The following is the +interface: + +[source,java] +---- +public interface Task { + // Consider changing the location of the three string values below + + String MESSAGE_DESCRIPTION_CONSTRAINTS = "Tasks Should have a non-empty description"; + + String MESSAGE_DURATION_CONSTRAINTS = "Duration must be a non-null value"; + + String MESSAGE_DATETIME_CONSTRAINTS = "Date and time must be a non-null value"; + + LocalDateTime getTaskDateTime(); + + String getStringTaskDateTime(); + + String getDescription(); + + String getDuration(); + + Entry getEntry(); +} +---- + +Below is a representation of the Task object. The form below is as an XmlAdaptedTask object +which allows the information contained in a Task object to be represented in a certain +format. Note that while the Task object has a Tuition Task and a Personal Task variant, +the XmlAdaptedTask object represented below embodies both aspects. Moreover, the +XmlAdaptedTask consists of String objects only, as opposed to the methods and variables +of various types that populate the Task interface above. + +image::taskStorage.jpg[width=""] + +// end::taskClass[] + +// tag::listCommand[] +. *listTask Command* [Minor Enhancement] + +This feature allows the user to see all tasks stored in the addressbook. It is useful when a user has entered a command +that displays only a single task. If the user then wants to see other tasks, he/she can input listTask +and display the tasks again. +// end::listCommand[] [appendix] == User Stories @@ -800,33 +995,82 @@ Priorities: High (must have) - `* * \*`, Medium (nice to have) - `* \*`, Low (un |Priority |As a ... |I want to ... |So that I can... |`* * *` |new user |see usage instructions |refer to instructions when I forget how to use the App -|`* * *` |user |add a new person | +|`* * *` |user |add a new person |update my list of contacts |`* * *` |user |delete a person |remove entries that I no longer need |`* * *` |user |find a person by name |locate details of persons without having to go through the entire list -|`* *` |user |hide <> by default |minimize chance of someone else seeing them by accident +|`* * *` |tutor |add a new tuition |so that I can compile my tuition schedule -|`*` |user with many persons in the address book |sort persons by name |locate a person easily -|======================================================================= +|`* * *` |user |add my personal task to the calendar |prevent any task from clashing + +|`* * *` |user |delete a task in the calendar |update my calender + +|`* * *` |user |edit a task in the calendar |update my calendar + +|`* * *` |tutor |list tasks I have in certain month |check and prepare myself in advance + +|`* * *` |tutor |add a contact as my tutee |separate my tutee from my personal contact lists + +|`* * *` |tutor |add subject grade of the tutees |keep track of his progress + +|`* * *` |tutor |add tutee's current education level |adjust my teaching accordingly + +|`* * *` |tutor |edit tutee's details |stay updated + +|`* * *` |user |use a calendar |manage my schedule effectively -_{More to be added}_ +|`* * *` |user |have a calender on application main window |view my schedule easily + +|`* * *` |tutor |change the calendar view page |view my schedule in terms of day, month, week or year + +|`* *` |tutor |sort my contact list by various categories |locate my contacts that have same characteristics easily + +|`* *` |tutor |list tutees who are learning same subject |determine the amount of tuition material copies to be printed + +|`* *` |tutor |list tutees with same education level |navigate within TuitionConnect easily + +|`* *` |tutor |list tutees coming from the same school |so that I can gather and spread useful information among these students. + +|`* *` |tutor |list tutees based on their grades |give more tuition to tutees who are doing badly + +|`* *` |tutor with many tutees |send messages to all tutee with same description |disperse messages easily + +|`* *` |user |submit feedback to the developers of the application |this application can be further improved + +|`* *` |tutor |hide <> by default |minimize chance of someone else seeing them by accident + +|`* *` |user |track my work hours over a specific interval of time |maintain a good work life balance + +|`* *` |tutor |track the tuition payment |know who has not paid + +|`* *` |tutor |add assignments(done and to-be-done) to a tutee |track his work and progress easily + +|`*` |tutor |measure the retention rate of my tutoring service |improve on myself and use it as my tuition credential + +|`*` |user |receive payments for my services electronically |receive payment more conveniently + +|`*` |tutor |take note of the name of my tutee's school teachers |adjust my teaching according to the school teacher. + +|`*` |tutor |insert my tutee's photo |recognise new students easily + +|`*` |tutor |delete all finished task |remove them permanently +|======================================================================= [appendix] == Use Cases -(For all use cases below, the *System* is the `AddressBook` and the *Actor* is the `user`, unless specified otherwise) +(For all use cases below, the *System* is the `TuitionConnect Application (TC)` and the *Actor* is the `user`, unless specified otherwise) -[discrete] -=== Use case: Delete person +=== Use case: UC01 - Delete person *MSS* 1. User requests to list persons -2. AddressBook shows a list of persons +2. TC shows a list of persons 3. User requests to delete a specific person in the list -4. AddressBook deletes the person +4. TC deletes the person + Use case ends. @@ -840,11 +1084,146 @@ Use case ends. * 3a. The given index is invalid. + [none] -** 3a1. AddressBook shows an error message. +** 3a1. TC shows an error message. + Use case resumes at step 2. -_{More to be added}_ +=== Use case: UC02 - Add a tuition schedule +*MSS* + +1. User adds a schedule by providing the time (date & duration) +2. TC asks whether user wants to *add a personal schedule(UC03)* or a tuition schedule. +3. User chooses tuition schedule +4. TC asks the name of the tutee +5. User types the name of the tutee +6. TC confirms that process is done. ++ +Use Case ends. + +*Extensions* + +[none] +* 1a. Clashing schedule ++ +[none] +** 1a1. TC reports that schedule clashes and requests a new timing. +** 1a2. User enters a new schedule +** Steps 1a1.-1a2. are repeated until there is no clash. +** Use case resumes from Step 2. + +* *a. At any time, user can cancel adding the schedule. +[none] +** *a1 TC requests confirmation of the cancelation. +** *a2. User confirms the cancellation. +** Use case ends. + +* 5a. Name is not found ++ +[none] +** 5a1. TC requests user to retype the tutee’s name. +** 5a2. User retypes the tutee’s name. +** Steps 5a1.-5a2. are repeated until a valid name is provided. +** Use case resumes from Step 6. + +* *a. At any time, user can cancel adding the schedule. +[none] +** *a1 TC requests confirmation of the cancelation. +** *a2. User confirms the cancellation. + Use case ends. + +=== Use case: UC03 - Add a personal schedule +*MSS* + +1. User adds a schedule by providing the time (date & duration) +2. TC asks whether user wants to add a personal schedule or to **add a tuition schedule(UC02)**. +3. User chooses personal schedule +4. TC prompts user to fill in a description. +5. User fills in the description. +6. TC confirms that process is done. ++ +Use Case ends. + +*Extension* + +[none] +* 1a. Clashing schedule ++ +[none] +** 1a1. TC reports that schedule clashes and requests a new timing. +** 1a2. User enters a new schedule +** Steps 1a1.-1a2. are repeated until there is no clash. +** Use case resumes from Step 2. + +* *a. At any time, user can cancel adding the schedule. +[none] +** *a1 TC requests confirmation of the cancelation. +** *a2. User confirms the cancellation. +** Use case ends. + +=== Use case: UC04 - Set a contact as a tutee +*MSS* + +1. User requests to set a contact as a new tutee by providing the tutee’s name +2. TC confirms that the process is done. ++ +Use case ends. + +*Extensions* +[none] +* 1a. The name does not exist in address book ++ +[none] +** 1a1. TC requests user to retype the tutee’s name. +** 1a2. User types in the name. +** Steps 1a1.- 1a2. are repeated until a valid name is provided. +** Use case resumes from Step 2. + +* *a. At any time, user can cancel the request. +[none] +** *a1 TC requests confirmation of the cancellation. +** *a2. User confirms the cancellation. +** Use case ends. + +* 2a. The name is already inside the tutee list. ++ +[none] +** 2a1. TC indicates that the name is already inside the tutee list. +** 2a2. User confirms the message. +** Use case ends. + +=== Use case: UC05 - Viewing schedule within a date range +*MSS* + +1. User requests to view schedule. +2. TC requests a date range. +3. User provides the desired date range. +4. TC lists all schedule within the date range. +5. User chooses to close the list. ++ +Use Case ends. + +*Extensions* + +[none] +* 3a. Invalid date range format is given. ++ +[none] +** 3a1. TC requests a valid date range input. +** 3a2. User provides the date range. +** Use case resumes from step 4. + +* *a. At any time, user can cancel adding the schedule. +[none] +** *a1 TC requests confirmation of the cancelation. +** *a2. User confirms the cancellation. +** Use case ends. + +* 4a. No schedule is created within the date range. +[none] +** 4a1. TC reports that no schedule exists within the time range. +** 4a2. User acknowledges. +** Use case ends. + [appendix] == Non Functional Requirements @@ -852,18 +1231,50 @@ _{More to be added}_ . Should work on any <> as long as it has Java `1.8.0_60` or higher installed. . Should be able to hold up to 1000 persons without a noticeable sluggishness in performance for typical usage. . A user with above average typing speed for regular English text (i.e. not code, not system admin commands) should be able to accomplish most of the tasks faster using commands than using the mouse. +. Users should prefer typing over mouse input or other input methods. +. Users should be comfortable using CLI apps. +. The app should not have flow flaws when running. +. The app may utilise third party libraries, API and plug-ins. +. The app should be able to access the Internet. +. The app should be able to perform basic commands without internet access. -_{More to be added}_ [appendix] == Glossary +[[address-book]] Address Book:: +An application for storing the contact details. + [[mainstream-os]] Mainstream OS:: Windows, Linux, Unix, OS-X +[[natural-language]] Natural Language:: +This refers to some predetermined common terms that may be provided as user input. + +[[person]] Person:: +A non tutee contact + +[[personal-task]] Personal Task:: +Non-tuition activity + [[private-contact-detail]] Private contact detail:: A contact detail that is not meant to be shared with others +[[task]] Task:: +This refers to the activities you want to add into your 'to-do-list' + +[[tuition-task]] Tuition Task:: +Teaching a specified tutee in contact list. + +[[tutee]] Tutee:: +A student whom you are giving tuition to + +[[tutee-details]] Tutee Details:: +These are details that are owned only by tutees, which include tutee's education level, grade, subject and school + +[[task-list]] Task List / Schedule:: +These terms are used interchangeably to refer to the list of tasks located in the middle of the app. + [appendix] == Product Survey diff --git a/docs/DummySearchPage.html b/docs/DummySearchPage.html deleted file mode 100644 index 1607d4c57291..000000000000 --- a/docs/DummySearchPage.html +++ /dev/null @@ -1,32 +0,0 @@ - - - - - Dummy Search Page - - - - Hi : This is a placeholder page for se-edu/addressbook-level4.
- You may update the code to load a page from a real service (e.g., Google search).
- This dummy page is used here because, given the high number of forks of this repo, loading a page from a real third-party service by default can result in that service taking counter-measures (e.g., redirecting to captcha pages) due to the high number of rapid requests received from a single IP.
- When you have made the change, please remove: -
    -
  1. This file (docs/DummySearchPage.html).
  2. -
  3. Task copyDummySearchPage in both build.gradle and .travis.yml.
  4. -
- - diff --git a/docs/LearningOutcomes.adoc b/docs/LearningOutcomes.adoc deleted file mode 100644 index cf153ba8b38f..000000000000 --- a/docs/LearningOutcomes.adoc +++ /dev/null @@ -1,265 +0,0 @@ -= Learning Outcomes -:toc: macro -:toc-title: -:toclevels: 1 -:sectnums: -:sectnumlevels: 1 -:imagesDir: images -:stylesDir: stylesheets -:repoURL: https://github.com/se-edu/addressbook-level4/tree/master - -After studying this code and completing the corresponding exercises, you should be able to, - -toc::[] - -''' - -== Use High-Level Designs `[LO-HighLevelDesign]` - -Note how the <> describes the high-level design using an _Architecture Diagrams_ and high-level sequence diagrams. - -*Resources* - -* https://se-edu.github.io/se-book/architecture/[se-edu/se-book: Design: Architecture] -* https://se-edu.github.io/se-book/design/introduction/multilevelDesign/[se-edu/se-book: Design: Introduction: Multi-Level Design] - -''' - -== Use Event-Driven Programming `[LO-EventDriven]` - -Note how the <> uses events to communicate with components without needing a direct coupling. Also note how the link:{repoURL}/src/main/java/seedu/address/commons/core/index/EventsCenter.java[`EventsCenter.java`] acts as an event dispatcher to facilitate communication between event creators and event consumers. - -*Resources* - -* https://se-edu.github.io/se-book/architecture/architecturalStyles/eventDriven/[se-edu/se-book: Design: Architecture: Architecture Styles: Event-Driven Architectural Style] - -''' - -== Use API Design `[LO-ApiDesign]` - -Note how components of AddressBook have well-defined APIs. For example, the API of the `Logic` component is given in the link:{repoURL}/src/main/java/seedu/address/logic/Logic.java[`Logic.java`] -image:LogicClassDiagram.png[width="800"] - -*Resources* - -* https://se-edu.github.io/se-book/reuse/apis/[se-edu/se-book: Implementation: Reuse: APIs] - -''' - -== Use Assertions `[LO-Assertions]` - -Note how the AddressBook app uses Java ``assert``s to verify assumptions. - -*Resources* - -* https://se-edu.github.io/se-book/errorHandling/assertions/[se-edu/se-book: Implementation: Error Handling: Assertions] - -=== Exercise: Add more assertions - -* Make sure assertions are enabled in your IDE by forcing an assertion failure (e.g. add `assert false;` somewhere in the code and run the code to ensure the runtime reports an assertion failure). -* Add more assertions to AddressBook as you see fit. - - -''' - -== Use Logging `[LO-Logging]` - -Note <>. - -*Resources* - -* https://se-edu.github.io/se-book/errorHandling/logging/[se-edu/se-book: Implementation: Error Handling: Logging] - -=== Exercise: Add more logging - -Add more logging to AddressBook as you see fit. - - -''' - -== Use Defensive Coding `[LO-DefensiveCoding]` - -Note how AddressBook uses the `ReadOnly*` interfaces to prevent objects being modified by clients who are not supposed to modify them. - -*Resources* - -* https://se-edu.github.io/se-book/errorHandling/defensiveProgramming/[se-edu/se-book: Implementation: Error Handling: Defensive Programming] - -=== Exercise: identify more places for defensive coding - -Analyze the AddressBook code/design to identify, - -* where defensive coding is used -* where the code can be more defensive - -''' - -== Use Build Automation `[LO-BuildAutomation]` - -Note <>. - -*Resources* - -* https://se-edu.github.io/se-book/integration/buildAutomation/what/[se-edu/se-book: Implementation: Integration: Build Automation: What] - -=== Exercise: Use gradle to run tasks - -* Use gradle to do these tasks: Run all tests in headless mode, build the jar file. - -=== Exercise: Use gradle to manage dependencies - -* Note how the build script `build.gradle` file manages third party dependencies such as ControlsFx. Update that file to manage a third-party library dependency. - - -''' - -== Use Continuous Integration `[LO-ContinuousIntegration]` - -Note <>. (https://travis-ci.org/se-edu/addressbook-level4[image:https://travis-ci.org/se-edu/addressbook-level4.svg?branch=master[Build Status]]) - -*Resources* - -* https://se-edu.github.io/se-book/integration/buildAutomation/continuousIntegrationDeployment/[se-edu/se-book: Implementation: Integration: Build Automation: CI & CD] - -=== Exercise: Use Travis in your own project - -* Set up Travis to perform CI on your own fork. - - -''' - -== Use Code Coverage `[LO-CodeCoverage]` - -Note how our CI server <>. (https://coveralls.io/github/se-edu/addressbook-level4?branch=master[image:https://coveralls.io/repos/github/se-edu/addressbook-level4/badge.svg?branch=master[Coverage Status]]) After <> for your project, you can visit Coveralls website to find details about the coverage of code pushed to your repo. https://coveralls.io/github/se-edu/addressbook-level4?branch=master[Here] is an example. - -*Resources* - -* https://se-edu.github.io/se-book/testing/testCoverage/[se-edu/se-book: QA: Testing: Test Coverage] - -=== Exercise: Use the IDE to measure coverage locally - -* Use the IDE to measure code coverage of your tests. - -''' - -== Apply Test Case Design Heuristics `[LO-TestCaseDesignHeuristics]` - -The link:{repoURL}/src/test/java/seedu/address/commons/util/StringUtilTest.java[`StringUtilTest.java`] -class gives some examples of how to use _Equivalence Partitions_, _Boundary Value Analysis_, and _Test Input Combination Heuristics_ to improve the efficiency and effectiveness of test cases testing the link:../src/main/java/seedu/address/commons/util/StringUtil.java[`StringUtil.java`] class. - -*Resources* - -* https://se-edu.github.io/se-book/testCaseDesign/[se-edu/se-book: QA: Test Case Design] - -=== Exercise: Apply Test Case Design Heuristics to other places - -* Use the test case design heuristics mentioned above to improve test cases in other places. - -''' - -== Write Integration Tests `[LO-IntegrationTests]` - -Consider the link:{repoURL}/src/test/java/seedu/address/storage/StorageManagerTest.java[`StorageManagerTest.java`] class. - -* Test methods `prefsReadSave()` and `addressBookReadSave()` are integration tests. Note how they simply test if The `StorageManager` class is correctly wired to its dependencies. -* Test method `handleAddressBookChangedEvent_exceptionThrown_eventRaised()` is a unit test because it uses _dependency injection_ to isolate the SUT `StorageManager#handleAddressBookChangedEvent(...)` from its dependencies. - -Compare the above with link:{repoURL}/src/test/java/seedu/address/logic/LogicManagerTest.java[`LogicManagerTest`]. Some of the tests in that class (e.g. `execute_*` methods) are neither integration nor unit tests. They are _integration + unit_ tests because they not only check if the LogicManager is correctly wired to its dependencies, but also checks the working of its dependencies. For example, the following two lines test the `LogicManager` but also the `Parser`. - -[source,java] ----- -@Test -public void execute_invalidCommandFormat_throwsParseException() { - ... - assertParseException(invalidCommand, MESSAGE_UNKNOWN_COMMAND); - assertHistoryCorrect(invalidCommand); -} ----- - -*Resources* - -* https://se-edu.github.io/se-book/testing/testingTypes/[se-edu/se-book: QA: Testing: Testing Types] - -=== Exercise: Write unit and integration tests for the same method. - -* Write a unit test for a high-level method somewhere in the code base (or a new method you wrote). -* Write an integration test for the same method. - -''' - -== Write System Tests `[LO-SystemTesting]` - -Note how tests below `src/test/java/systemtests` package (e.g link:{repoURL}/src/test/java/systemtests/AddCommandSystemTest.java[`AddCommandSystemTest.java`]) are system tests because they test the entire system end-to-end. - -*Resources* - -* https://se-edu.github.io/se-book/testing/testingTypes/[se-edu/se-book: QA: Testing: Testing Types] - -=== Exercise: Write more system tests - -* Write system tests for the new features you add. - -''' - -== Automate GUI Testing `[LO-AutomateGuiTesting]` - -Note how this project uses TextFX library to automate GUI testing, including <>. - -=== Exercise: Write more automated GUI tests - -* Covered by `[LO-SystemTesting]` - -''' - -== Apply Design Patterns `[LO-DesignPatterns]` - -Here are some example design patterns used in the code base. - -* *Singleton Pattern* : link:{repoURL}/src/main/java/seedu/address/commons/core/EventsCenter.java[`EventsCenter.java`] is Singleton class. Its single instance can be accessed using the `EventsCenter.getInstance()` method. -* *Facade Pattern* : link:{repoURL}/src/main/java/seedu/address/storage/StorageManager.java[`StorageManager.java`] is not only shielding the internals of the Storage component from outsiders, it is mostly redirecting method calls to its internal components (i.e. minimal logic in the class itself). Therefore, `StorageManager` can be considered a Facade class. -* *Command Pattern* : The link:{repoURL}/src/main/java/seedu/address/logic/commands/Command.java[`Command.java`] and its sub classes implement the Command Pattern. -* *Observer Pattern* : The <> used by this code base employs the Observer pattern. For example, objects that are interested in events need to have the `@Subscribe` annotation in the class (this is similar to implementing an `\<>` interface) and register with the `EventsCenter`. When something noteworthy happens, an event is raised and the `EventsCenter` notifies all relevant subscribers. Unlike in the Observer pattern in which the `\<>` class is notifying all `\<>` objects, here the `\<>` classes simply raises an event and the `EventsCenter` takes care of the notifications. -* *MVC Pattern* : -** The 'View' part of the application is mostly in the `.fxml` files in the `src/main/resources/view` folder. -** `Model` component contains the 'Model'. However, note that it is possible to view the `Logic` as the model because it hides the `Model` behind it and the view has to go through the `Logic` to access the `Model`. -** Sub classes of link:{repoURL}/src/main/java/seedu/address/ui/UiPart.java[`UiPart`] (e.g. `PersonListPanel` ) act as 'Controllers', each controlling some part of the UI and communicating with the 'Model' (via the `Logic` component which sits between the 'Controller' and the 'Model'). -* *Abstraction Occurrence Pattern* : Not currently used in the app. - -*Resources* - -* https://se-edu.github.io/se-book/designPatterns/[se-edu/se-book: Design: Design Patterns] - -=== Exercise: Discover other possible applications of the patterns - -* Find other possible applications of the patterns to improve the current design. e.g. where else in the design can you apply the Singleton pattern? -* Discuss pros and cons of applying the pattern in each of the situations you found in the previous step. - -=== Exercise: Find more applicable patterns - -* Learn other _Gang of Four_ Design patterns to see if they are applicable to the app. - -''' - -== Use Static Analysis `[LO-StaticAnalysis]` - -Note how this project uses the http://checkstyle.sourceforge.net/[CheckStyle] static analysis tool to confirm compliance with the coding standard. - -*Resources* - -* https://se-edu.github.io/se-book/qualityAssurance/staticAnalysis/[se-edu/se-book: QA: Static Analysis] - -=== Exercise: Use CheckStyle locally to check style compliance - -* Install the CheckStyle plugin for your IDE and use it to check compliance of your code with our style rules (given in `/config/checkstyle/checkstyle.xml`). - -''' - -== Do Code Reviews `[LO-CodeReview]` - -* Note how some PRs in this project have been reviewed by other developers. Here is an https://github.com/se-edu/addressbook-level4/pull/147[example]. -* Also note how we have used https://www.codacy.com[Codacy] to do automate some part of the code review workload (https://www.codacy.com/app/damith/addressbook-level4?utm_source=github.com&utm_medium=referral&utm_content=se-edu/addressbook-level4&utm_campaign=Badge_Grade[image:https://api.codacy.com/project/badge/Grade/fc0b7775cf7f4fdeaf08776f3d8e364a[Codacy Badge]]) - - -=== Exercise: Review a PR - -* Review PRs created by team members. diff --git a/docs/UserGuide.adoc b/docs/UserGuide.adoc index 74248917e438..87008fa8b8ca 100644 --- a/docs/UserGuide.adoc +++ b/docs/UserGuide.adoc @@ -1,4 +1,4 @@ -= AddressBook Level 4 - User Guide += TuitionConnect (TC) - User Guide :toc: :toc-title: :toc-placement: preamble @@ -11,13 +11,26 @@ ifdef::env-github[] :tip-caption: :bulb: :note-caption: :information_source: endif::[] -:repoURL: https://github.com/se-edu/addressbook-level4 +:repoURL: https://github.com/CS2103JAN2018-T11-B1/main -By: `Team SE-EDU` Since: `Jun 2016` Licence: `MIT` +By: `Team MarvelousBook` Since: `Feb 2018` Licence: `MIT` +// tag::introduction[] == Introduction -AddressBook Level 4 (AB4) is for those who *prefer to use a desktop app for managing contacts*. More importantly, AB4 is *optimized for those who prefer to work with a Command Line Interface* (CLI) while still having the benefits of a Graphical User Interface (GUI). If you can type fast, AB4 can get your contact management tasks done faster than traditional GUI apps. Interested? Jump to the <> to get started. Enjoy! +TuitionConnect (TC) is an integration of a personalised address book, task manager and income tracker for private home tutors. + + +Stems from our strong passion in simplifying the life of tutors, TuitionConnect introduces features +to aid management of tutee contacts, tuition schedule and income tracking. Moreover, we believe that a thorough connectivity is the +root of simplicity. Hence, there are also features which support the management of your personal contacts and tasks. + + +TuitionConnect is primarily for tutors who *prefer to use a desktop app for managing contacts and tasks*. +More importantly, TuitionConnect is *optimized for those who prefer to work with a Command Line Interface* +(CLI) while still having the benefits of a Graphical User Interface (GUI). + + +With TuitionConnect's keyboard shortcuts and productive features, TuitionConnect can get your contact and task management done faster than traditional GUI apps. Interested? +Jump to the <> to get started. Enjoy! +// end::introduction[] == Quick Start @@ -27,23 +40,42 @@ AddressBook Level 4 (AB4) is for those who *prefer to use a desktop app for mana Having any Java 8 version is not enough. + This app will not work with earlier versions of Java 8. + -. Download the latest `addressbook.jar` link:{repoURL}/releases[here]. -. Copy the file to the folder you want to use as the home folder for your Address Book. +. Download the latest `TuitionConnect.jar` link:{https://github.com/CS2103JAN2018-T11-B1/main/releases}/releases[here]. +. Copy the file to the folder you want to use as the home folder for TuitionConnect. . Double-click the file to start the app. The GUI should appear in a few seconds. + -image::Ui.png[width="790"] +// tag::ui_overview[] +image::Ui_with_overview.jpg[width="650"] +// end::ui_overview[] +[NOTE] +If "license check has failed" message pops up, simply click "OK". + . Type the command in the command box and press kbd:[Enter] to execute it. + e.g. typing *`help`* and pressing kbd:[Enter] will open the help window. . Some example commands you can try: * *`list`* : lists all contacts -* **`add`**`n/John Doe p/98765432 e/johnd@example.com a/John street, block 123, #01-01` : adds a contact named `John Doe` to the Address Book. +* **`add`**`n/John Doe p/98765432 e/johnd@example.com a/John street, block 123, #01-01` : adds a contact named `John Doe` to the address book. * **`delete`**`3` : deletes the 3rd contact shown in the current list * *`exit`* : exits the app . Refer to <> for details of each command. +// tag::conceptintro[] +== Concepts Introduction +* Contact : This refers to both the tutee and person. +* Tutee : This refers to the students you are teaching. Adding a student as tutee requires specific *tutee details* to be provided. + By doing so, it will be easier to organize your contacts using our special features. + +* Person : Other contacts which are not tutee. + +* Tutee Details : These are details that are only owned by tutees, which include tutee's education level, grade, subject and school. + +* Task : This refers to the activities you want to add into your 'to-do-list'. + +Type of Task: + +. Tuition task: Teaching a specified tutee in contact list. + +. Personal task: Non-tuition activity. +* Address Book : This refers to the list of contacts located on the most left side of the app. +* Task List / Schedule : these terms are used interchangeably to refer to the list of tasks located in the middle of the app. +// end::conceptintro[] + [[Features]] == Features @@ -63,7 +95,10 @@ Format: `help` === Adding a person: `add` Adds a person to the address book + -Format: `add n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS [t/TAG]...` +Using Command Word: + +Format: `add n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS [t/TAG]...` + +Using Command Alias: + +Format: `a n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS [t/TAG]...` + [TIP] A person can have any number of tags (including 0) @@ -72,59 +107,181 @@ Examples: * `add n/John Doe p/98765432 e/johnd@example.com a/John street, block 123, #01-01` * `add n/Betsy Crowe t/friend e/betsycrowe@example.com a/Newgate Prison p/1234567 t/criminal` +* `a n/Dickson ee p/91234567 e/dickson@exmaple.com a/Dickson street, block 456, #02-02` -=== Listing all persons : `list` +// tag::addtutee[] +=== Adding a tutee: `addtutee` +Adds a tutee to the address book + +Using Command Word: + +Format: `addtutee n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS s/SUBJECT g/GRADE edu/EDUCATION_LEVEL sch/SCHOOL [t/TAG]...` -Shows a list of all persons in the address book. + -Format: `list` +[TIP] +==== +* A tutee can have any number of tags (including 0) +* Special character in grade can be anything since different institution may use different grading system. + +i.e. B+, B3, B$, B# etc are all valid +==== -=== Editing a person : `edit` +[WARNING] +==== +* Education level can only be either primary, secondary or junior college (NOT case sensitive) + +* All details except tag MUST be added +==== -Edits an existing person in the address book. + -Format: `edit INDEX [n/NAME] [p/PHONE] [e/EMAIL] [a/ADDRESS] [t/TAG]...` +Examples: -**** -* Edits the person at the specified `INDEX`. The index refers to the index number shown in the last person listing. The index *must be a positive integer* 1, 2, 3, ... -* At least one of the optional fields must be provided. -* Existing values will be updated to the input values. -* When editing tags, the existing tags of the person will be removed i.e adding of tags is not cumulative. -* You can remove all the person's tags by typing `t/` without specifying any tags after it. -**** +* `addtutee n/John Doe p/98765432 e/johnd@example.com a/John street, block 123, #01-01 s/Economics g/B+ edu/junior college sch/Victoria Junior College` +* `addtutee n/Betsy Crowe s/Mathematics g/C e/betsycrowe@example.com a/Newgate Town p/1234567 t/owesMoney sch/Victoria Institution edu/secondary` +* `addtutee n/Dickson ee p/91234567 e/dickson@exmaple.com a/Dickson street, block 456, #02-02 s/english g/f9 edu/primary sch/Newgate Primary School` +// end::addtutee[] + +=== Listing all contacts : `list` + +Shows a list of all contacts in the address book. + +Using Command Word: + +Format: `list` + +Using Command Alias: + +Format: `l` + +// tag::listtutee[] +=== Listing all tutees : `listtutee` + +Shows a list of all tutees in the address book. + +Using Command Word: + +Format: `listtutee` + +// end::listtutee[] + +// tag::edit[] +=== Editing a contact : `edit` + +Edits existing contact's details in the address book. + +Using Command Word: + +Format: `edit INDEX [n/NAME] [p/PHONE] [e/EMAIL] [a/ADDRESS] [s/SUBJECT] [s/SUBJECT] [g/GRADE] [edu/EDUCATION_LEVEL] [sch/SCHOOL] [t/TAG]...` + +Using Comamnd Alias: + +Format: `e INDEX [n/NAME] [p/PHONE] [e/EMAIL] [a/ADDRESS] [s/SUBJECT] [s/SUBJECT] [g/GRADE] [edu/EDUCATION_LEVEL] [sch/SCHOOL] [t/TAG]...` + + +[width="80%",cols="20%, 60%",options="header",] +|======================================================================= +|Keyword |Description +|edit |To call the edit command +|INDEX |Index number shown in the last person listing. It *must be a positive integer* 1, 2, 3, ... +|INDEX |The contact at the specified `INDEX` will be edited +|======================================================================= + +[NOTE] +==== +* "Tutee" tag is specific to tutee only and will never be removed. +* Remove all the person's tags: type `t/` without specifying any tags after it. +* Remove all the tutee's tags except "Tutee" tag: type `t/` without specifying any tags after it OR type `t/tutee` (this includes other variation of case letters for tutee such as "TUTEE", "TuteE" etc). +==== + +[underline]#*Valid Details to edit for each type of contact:*# +[width="80%",cols="40%, 40%",options="header",] +|======================================================================= +|Person |Tutee +|NAME |NAME +|PHONE |PHONE +|EMAIL |EMAIL +|ADDRESS |ADDRESS +| --- |SUBJECT +| --- |GRADE +| --- |EDUCATION_LEVEL +| --- |SCHOOL +|TAG |TAG +|======================================================================= + +[WARNING] +==== +* At least one of the valid optional fields must be provided. +* Existing information will be updated to the new information. +* When editing tags, the existing tags of the contact will be removed (Except "Tutee" tag) i.e adding of tags is not cumulative. +* Manual adding of "Tutee" tag to a person is not allowed (this includes other variation of case letters for tutee such as "tutee", "TuTeE" etc) +* Editing of subject, grade, education level and school is not allowed for person +==== Examples: * `edit 1 p/91234567 e/johndoe@example.com` + -Edits the phone number and email address of the 1st person to be `91234567` and `johndoe@example.com` respectively. -* `edit 2 n/Betsy Crower t/` + -Edits the name of the 2nd person to be `Betsy Crower` and clears all existing tags. +Edits the phone number and email address of the 1st contact to be `91234567` and `johndoe@example.com` respectively. +* `e 2 n/Betsy Crower sch/JohnDoe Secondary t/` + +Edits the name of the 2nd contact (a tutee) to be `Betsy Crower`, her school to `JohnDoe Secondary` and clears all existing tags (except "Tutee" tag). +// end::edit[] -=== Locating persons by name: `find` +// tag::findperson[] +=== Locating contacts by category: `findpersonby` -Finds persons whose names contain any of the given keywords. + -Format: `find KEYWORD [MORE_KEYWORDS]` +Finds contacts whose specified category contain any of the given keywords. + +Using Command Word: + +Format: `findpersonby CATEGORY KEYWORD [MORE_KEYWORDS]` + +Using Command Alias: + +Format: `f CATEGORY KEYWORD [MORE_KEYWORDS]` + **** * The search is case insensitive. e.g `hans` will match `Hans` * The order of the keywords does not matter. e.g. `Hans Bo` will match `Bo Hans` -* Only the name is searched. +* There are 5 categories to pick: +** 1. name +** 2. edu* (for education level) +** 3. grade* +** 4. school* +** 5. subject* +* Categories marked with * are only owned by tutees (tutee details). * Only full words will be matched e.g. `Han` will not match `Hans` -* Persons matching at least one keyword will be returned (i.e. `OR` search). e.g. `Hans Bo` will return `Hans Gruber`, `Bo Yang` +* Contacts matching at least one keyword will be returned (i.e. `OR` search). + e.g. `Hans Bo` will return `Hans Gruber`, `Bo Yang` **** Examples: -* `find John` + -Returns `john` and `John Doe` -* `find Betsy Tim John` + -Returns any person having names `Betsy`, `Tim`, or `John` +* `findpersonby Name John` + +Lists `john` and `John Doe` +* `findpersonby name Betsy Tim John` + +Lists any contact having names `Betsy`, `Tim`, or `John` +* `f grade B B+` + +Lists all tutee with grade `B` and `B+` +// end::findperson[] -=== Deleting a person : `delete` +// tag::sortperson[] +=== Sorting persons by category: `sortpersonby` -Deletes the specified person from the address book. + -Format: `delete INDEX` +Sorts your displayed contact list according to a specified category in ascending order (lexicographically). + +Using Command Word: + +Format: `sortpersonby CATEGORY` + +Using Command Alias: + +Format: `spb CATEGORY` + **** -* Deletes the person at the specified `INDEX`. +* There are 5 categories to pick: +** 1. name +** 2. edu* +** 3. grade* +** 4. school* +** 5. subject* +* Categories marked with * are only owned by tutees. If one of these is selected, all non-tutees will be displayed + last after their names are sorted lexicographically. +* Sorting category types is case insensitive. + e.g both `sort name` and `sort Name` works fine. +* The sorting is case insensitive. + e.g In a case where `sort name` is writen, `angel' is diplayed before 'Ben'. +* The displayed contact list will be sorted; not the entire contact list. +**** + +Example: + + +* `sortpersonby Name` + +sorts by name lexicographically. +// end::sortperson[] + +=== Deleting a contact : `delete` + +Deletes the specified contact from the address book. + +Using Command Word: + +Format: `delete INDEX` + +Using Command Alias: + +Format: `d INDEX` + +**** +* Deletes the contact at the specified `INDEX`. * The index refers to the index number shown in the most recent listing. * The index *must be a positive integer* 1, 2, 3, ... **** @@ -133,35 +290,237 @@ Examples: * `list` + `delete 2` + -Deletes the 2nd person in the address book. -* `find Betsy` + -`delete 1` + -Deletes the 1st person in the results of the `find` command. +Deletes the 2nd contact in the address book. +* `f Betsy` + +`d 1` + +Deletes the 1st contact in the results of the `f` command. -=== Selecting a person : `select` +// tag::addtuition[] +=== Adding a tuition task: `addtuition` -Selects the person identified by the index number used in the last person listing. + -Format: `select INDEX` +Adds a task to TuitionConnect's schedule + +Using Command Word: + +Format: `addtuition TUTEE_INDEX DATE TIME DURATION [DESCRIPTION] + **** -* Selects the person and loads the Google search page the person at the specified `INDEX`. -* The index refers to the index number shown in the most recent listing. -* The index *must be a positive integer* `1, 2, 3, ...` +*PARAMETERS* + +* TUTEE_INDEX +** The associated tutee is chosen by specifying a `TUTEE_INDEX`. The index refers to the index number shown in +the left side of person listing. The index *must be a positive integer* 1, 2, 3, ... +** The chosen index must be the index of a Tutee (persons who have 'Tutee' tag). Refer to the *addtutee* command guide +for more information about Tutee. + +* DATE +** Date refers to the date of the tuition. +** Date must be written in the format of dd/mm/yyyy +*** valid example : 08/03/2018 +*** invalid example : 8/3/2018 + +* TIME +** Time refers to the starting time of the tuition. +** Time must be written in the format of hh:mm (24-hr format). +*** valid example : 12:00 +*** invalid example : 25:00 + +* DURATION +** Duration refers to the duration of the tuition. +** Duration must be written in the format of XXhXXm +*** valid example : 1h30m +**** this means that the tuition lasts for 1 *hour* and 30 *minute*. +*** invalid example : 1h30m +**** the duration in hour cannot have leading zero +*** invalid example : 1h60m +**** the duration in minute must be a number between 0 and 59. +*** invalid example : 30m +**** a correct duration for a 30 minute tuition is *0h30m*. + +* DESCRIPTION +** Description can be of any format. +** Leading and trailing whitespaces will be removed. +** Description is optional. It can be empty. **** Examples: -* `list` + -`select 2` + -Selects the 2nd person in the address book. -* `find Betsy` + -`select 1` + -Selects the 1st person in the results of the `find` command. +* `addtuition 1 10/05/2018 09:00 1h30m Calculus homework page 24` +* `addtuition 1 31/12/2018 16:00 0h30m` +// end::addtuition[] + +// tag::addtask[] +=== Adding a personal task: `addtask` + +Adds a personal task to the TuitionConnect schedule + +Using Command Word: + +Format: `addtask DATE TIME DURATION [DESCRIPTION]+ + +**** +The parameters (DATE, TIME, DURATION AND DESCRIPTION) have the same structure and limitations as explained in *addtask* command guide. +**** + +Examples: + +* `addtask 10/05/2018 09:00 1h30m Outing with friend` +* `addtask 31/12/2018 16:00 0h30m` +// end::addtask[] + +// tag::listTask[] +=== Listing all tasks : `listtask` +Shows a list of all tasks in the task list. + +Using Command Word: + +Format: `listtask` + +Using Command Alias: + +Format: `lt` + +The purpose of the listtask command is to regenerate + +the complete list of tasks for a user in the event that + +only a specific set of tasks were displayed. +// end::listTask[] + +// tag::deletetask[] +=== Deleting a task: `deletetask` + +Deletes a task from the TuitionConnect schedule + +Using Command Word: + +Format: `deletetask TASK_INDEX` + +** The task is chosen by specifying a `TASK_INDEX`. The index refers to the index number shown on +the left side of the schedule list. The index *must be a positive integer* 1, 2, 3, ... + +Examples: + +* `deletetask 1` +// end::deletetask[] + +// tag::findtask[] +=== Locating tasks by category: `findtaskby` + +Finds tasks which specified category has value within 2 given boundaries (inclusive) or contain any of the given keywords. + +The functionality is determined according to the specified FIND_TYPE. + + +Using Command Word: + +Format: `findtaskby CATEGORY FIND_TYPE KEYWORD [MORE_KEYWORDS]` + + +**** +* *PARAMETERS* + +* CATEGORY +** There are 3 categories available: +*** 1. month +*** 2. year `(coming in v2.0)` +*** 3. duration `(coming in v2.0)` + +* FIND_TYPE +** Choice of Find Type determines the behaviour of this command and the subsequent parameters. +** There are 2 choices of Find Type: +*** 1. 'between' : returns tasks within the 2 given boundaries (inclusive). +*** 2. 'namely' : returns tasks that match the keywords. + +* KEYWORDS +** Keywords given depend on the specified category: +*** 1. *month* +**** months in either M, MM, MMM or MMMM format. + +e.g. '1', '01', 'jan' and 'january' are all acceptable. +**** several common terms such as 'now', 'today', 'this month', 'current month', 'last month' and 'next month'. +*** 2. *year* to be implemented in v2.0 +*** 3. *duration* to be implemented in v2.0 +** The amount of keywords depend on the specified Find Type: +*** 1. *between* + +Exactly *2 different* keywords must be provided as the boundary values. + +[NOTE] +common terms that refer to a same time are considered as same keywords. + +e.g. `findtaskby month between today this month` is an invalid command. +*** 2. *namely* + +Any amount can be provided. If *0* keyword is given, the displayed schedule will be empty. +**** + +Examples: + +* `findtaskby month between january current month` + +Lists all tasks scheduled from January until the current month. +* `findtaskby month between November February` + +Lists all tasks scheduled on November, December, January and February. +* `findtaskby month between now last month` + +Lists all tasks. +* `f month namely 2 06 oct +Lists all tasks scheduled on February, June and October. +// end::findtask[] + +// tag::sorttask[] +=== Sorting tasks by category: `sorttaskby` + +Sorts your displayed task list according to a specified category in ascending order. + +Using Command Word: + +Format: `sorttaskby CATEGORY` + +Using Command Alias: + +Format: `stb CATEGORY` + + +**** +* There are 3 categories to pick: +** 1. datetime +** 2. month +** 3. duration [coming in V 2.0] +* Sorting category types is case insensitive. +* Sorting is in increasing order. This means later is placed below the earlier. + e.g both `sort month` and `sort Month` works fine. +**** + +Example: + + +* `sorttaskby Month` + +sorts tasks by month in increasing order. +// end::sorttask[] + +// tag::change[] +=== Changing calendar view page time unit: `change` + +Changes the calendar's view page into the time unit specified by you. +Using Command Word: + +Format: `change TIME_UNIT` + +[width="59%",cols="22%,<23%, 50%",options="header",] +|======================================================================= +|Time Unit |User input |Description +|Day |d |View the calendar in day +|Week |w |View the calendar in week +|Month |m |View the calendar in month +|Year |y |View the calendar in year +|======================================================================= + +[NOTE] +==== +* Default view page time unit is d. +* Command fails if current view page time unit is same as the user input time unit or user input an unrecognised time unit. +==== + +Examples: + +* `change d` (command fails as current calendar view is already in day) ++ +image::change-d.png[width="600" height="400"] ++ +* `change w` (changes calendar view to week) ++ +image::change-w.png[width="600" height="400"] ++ +* `change m` (changes calendar view to month) ++ +image::change-m.png[width="600" height="400"] ++ +* `change y` (changes calendar view to year) ++ +image::change-y.png[width="600" height="400"] ++ +[NOTE] +Year view is unable to display all the months due to third party software constraint. +// end::change[] === Listing entered commands : `history` Lists all the commands that you have entered in reverse chronological order. + -Format: `history` +Using Command Word: + +Format: `history` + +Using Command Alias: + +Format: `h` [NOTE] ==== @@ -171,12 +530,15 @@ Pressing the kbd:[↑] and kbd:[↓] arrows will display the previous and // tag::undoredo[] === Undoing previous command : `undo` -Restores the address book to the state before the previous _undoable_ command was executed. + +Restores the address book and the task list to the state before the previous _undoable_ command was executed. + +Using Command Word: + Format: `undo` +Using Command Alias: + +Format: `u` [NOTE] ==== -Undoable commands: those commands that modify the address book's content (`add`, `delete`, `edit` and `clear`). +Undoable commands: those commands that modify the address book's and task list's content (`add`, `addtutee`, `addtask`, `addtuition`, `delete`, `edit` and `clear`). ==== Examples: @@ -185,20 +547,18 @@ Examples: `list` + `undo` (reverses the `delete 1` command) + -* `select 1` + -`list` + -`undo` + -The `undo` command fails as there are no undoable commands executed previously. - * `delete 1` + `clear` + -`undo` (reverses the `clear` command) + -`undo` (reverses the `delete 1` command) + +`u` (reverses the `clear` command) + +`u` (reverses the `delete 1` command) + === Redoing the previously undone command : `redo` Reverses the most recent `undo` command. + -Format: `redo` +Using Command Word: + +Format: `redo` + +Using Command Alias: + +Format: `r` Examples: @@ -213,15 +573,18 @@ The `redo` command fails as there are no `undo` commands executed previously. * `delete 1` + `clear` + `undo` (reverses the `clear` command) + -`undo` (reverses the `delete 1` command) + +`u` (reverses the `delete 1` command) + `redo` (reapplies the `delete 1` command) + -`redo` (reapplies the `clear` command) + +`r` (reapplies the `clear` command) + // end::undoredo[] === Clearing all entries : `clear` -Clears all entries from the address book. + -Format: `clear` +Clears all entries in TuitionConnect. + +Using Command Word: + +Format: `clear` + +Using Command Alias: + +Format: `c` === Exiting the program : `exit` @@ -230,35 +593,103 @@ Format: `exit` === Saving the data -Address book data are saved in the hard disk automatically after any command that changes the data. + +TuitionConnect data are saved in the hard disk automatically after any command that + changes the data. + There is no need to save manually. +// tag::proposedfeatures[] +=== View tutee fees `[coming in v2.0]` +Views the amount that has to be paid by a tutee. + +// tag::encryption[] +=== Encrypting data files +Address book data are automatically encrypted after any command that +changes the data. + +=== View tutee fees `[coming in v2.0] +View the current amount that has to be paid by a tutee. + +=== View tuition progress `[coming in v2.0]` +Shows the details of tuition provided within several months in the form of a graph. +// end::proposedfeatures[] -// tag::dataencryption[] -=== Encrypting data files `[coming in v2.0]` +// tag::ChoChihTunV2.0[] +=== Update tutee fees `[coming in v2.0]` +Updates the remaining balance of the tuition fees of a specific tutee after he or she has paid. + +=== Complete task `[coming in v2.0]` +Marks a task as completed. If the task is tuition, fees will automatically be added to the tutee's fee balance. + +=== Edit task `[coming in v2.0]` +Edits date, time, duration or description of an existing task and reflects the changes on the calendar. + +=== View contact address on Google map `[coming in v2.0]` +Views the location of a specific contact's address on google map and the shortest route from the current location will be displayed. + + +=== Select a contact or a task `[coming in v2.0]` +Selects either a contact or a task and a pop up page containing all the relevant information will appear. +// end::ChoChihTunV2.0[] -_{explain how the user can enable/disable data encryption}_ -// end::dataencryption[] == FAQ *Q*: How do I transfer my data to another Computer? + -*A*: Install the app in the other computer and overwrite the empty data file it creates with the file that contains the data of your previous Address Book folder. +*A*: Install the app in the other computer and overwrite the empty data file it creates with the file that contains the data of your previous TuitionConnect folder. == Command Summary +Using Command Word: + * *Add* `add n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS [t/TAG]...` + -e.g. `add n/James Ho p/22224444 e/jamesho@example.com a/123, Clementi Rd, 1234665 t/friend t/colleague` -* *Clear* : `clear` -* *Delete* : `delete INDEX` + -e.g. `delete 3` -* *Edit* : `edit INDEX [n/NAME] [p/PHONE_NUMBER] [e/EMAIL] [a/ADDRESS] [t/TAG]...` + -e.g. `edit 2 n/James Lee e/jameslee@example.com` -* *Find* : `find KEYWORD [MORE_KEYWORDS]` + -e.g. `find James Jake` -* *List* : `list` -* *Help* : `help` -* *Select* : `select INDEX` + -e.g.`select 2` -* *History* : `history` -* *Undo* : `undo` -* *Redo* : `redo` +e.g. `add n/James Ho p/22224444 e/jamesho@example.com a/123, Clementi Rd, 1234665 t/friend t/colleague` + +* *Add Tutee* `addtutee n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS s/SUBJECT g/GRADE edu/EDUCATION_LEVEL sch/SCHOOL [t/TAG]...` + +e.g. `add n/James Ho p/22224444 e/jamesho@example.com a/123, Clementi Rd, 1234665 s/Mathematics g/A edu/junior college sch/National Junior College t/priority` + +* *Clear* : `clear` + +* *Delete* : `delete PERSON_INDEX` + +e.g. `delete 3` + +* *Edit* : `edit INDEX [n/NAME] [p/PHONE_NUMBER] [e/EMAIL] [a/ADDRESS] [s/SUBJECT] [s/SUBJECT] [g/GRADE] [edu/EDUCATION_LEVEL] [sch/SCHOOL] [t/TAG]...` + +e.g. `edit 2 n/James Lee e/jameslee@example.com` + +* *Find Person By* : findpersonby CATEGORY KEYWORD [MORE_KEYWORDS]` + +e.g. `findpersonby name Jake Doe` + +* *Sort Person By* : sortpersonby CATEGORY + +e.g. `sort grade` +* *List* : `list` + +* *List Tutee* : 'listtutee' + +* *Help* : `help` + +* *Add Tuition* : addtuition TUTEE_INDEX DATE TIME DURATION [DESCRIPTION] + +e.g. `addtuition 1 10/05/2018 12:00 1h30m Calculus homework page 24` + +* *Add Task* : 'add DATE TIME DURATION [DESCRIPTION] + +e.g. `addtask 10/05/2018 12:00 1h30m Going out with friends` + +* *List Task* : 'listtask` + +* *Delete Task* : 'delete TASK_INDEX` + +e.g. `delete 1` + +* *Find Task By* : findtaskby CATEGORY FIND_TYPE KEYWORD [MORE_KEYWORDS]` + +e.g. `findtaskby month between April October` + +* *Sort Task By* : sorttaskby CATEGORY + +e.g. `sorttaskby datetime` + +* *Change* : `change TIME_UNIT` + +e.g. `change w` + +* *History* : `history` + +* *Undo* : `undo` + +* *Redo* : `redo` + + +Using Command Alias: + +* *Add* `a n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS [t/TAG]...` + +e.g. `a n/James Ho p/22224444 e/jamesho@example.com a/123, Clementi Rd, 1234665 t/friend t/colleague` + +* *Clear* : `c` + +* *Delete* : `d INDEX` + +e.g. `d 3` + +* *Edit* : `e INDEX [n/NAME] [p/PHONE_NUMBER] [e/EMAIL] [a/ADDRESS] [s/SUBJECT] [s/SUBJECT] [g/GRADE] [edu/EDUCATION_LEVEL] [sch/SCHOOL] [t/TAG]...` + +e.g. `e 2 n/James Lee e/jameslee@example.com` + +* *Find Person By* : f CATEGORY KEYWORD [MORE_KEYWORDS]` + +e.g. `f name Jake Doe` + +* *List* : `l` + +* *Sort Person By* : `spb CATEGORY` + +e.g. `spb grade` + +* *ListTask* : `lt` + +* *Sort Task By* : `stb CATEGORY` + +e.g. `stb datetime` + +* *Help* : `help` + +* *History* : `h` + +* *Undo* : `u` + +* *Redo* : `r` + diff --git a/docs/images/AddTuteeActivityDiagram.png b/docs/images/AddTuteeActivityDiagram.png new file mode 100644 index 000000000000..465221be4d19 Binary files /dev/null and b/docs/images/AddTuteeActivityDiagram.png differ diff --git a/docs/images/AddTuteeCommandSequenceDiagram.PNG b/docs/images/AddTuteeCommandSequenceDiagram.PNG new file mode 100644 index 000000000000..72f16f9e02bb Binary files /dev/null and b/docs/images/AddTuteeCommandSequenceDiagram.PNG differ diff --git a/docs/images/LogicCompleteCommandClassDiagram.jpg b/docs/images/LogicCompleteCommandClassDiagram.jpg new file mode 100644 index 000000000000..181da1e50c18 Binary files /dev/null and b/docs/images/LogicCompleteCommandClassDiagram.jpg differ diff --git a/docs/images/ModelClassDiagram2.png b/docs/images/ModelClassDiagram2.png new file mode 100644 index 000000000000..06473be34629 Binary files /dev/null and b/docs/images/ModelClassDiagram2.png differ diff --git a/docs/images/SortPersonSequenceDiagram.png b/docs/images/SortPersonSequenceDiagram.png new file mode 100644 index 000000000000..ac42fbc4dc70 Binary files /dev/null and b/docs/images/SortPersonSequenceDiagram.png differ diff --git a/docs/images/StorageClassDiagram2.png b/docs/images/StorageClassDiagram2.png new file mode 100644 index 000000000000..3fc65ed974ae Binary files /dev/null and b/docs/images/StorageClassDiagram2.png differ diff --git a/docs/images/TuteeClassDiagram.JPG b/docs/images/TuteeClassDiagram.JPG new file mode 100644 index 000000000000..bb396ff3c4e4 Binary files /dev/null and b/docs/images/TuteeClassDiagram.JPG differ diff --git a/docs/images/Ui.png b/docs/images/Ui.png index 5ec9c527b49c..c9479f6fb6d6 100644 Binary files a/docs/images/Ui.png and b/docs/images/Ui.png differ diff --git a/docs/images/Ui_with_overview.jpg b/docs/images/Ui_with_overview.jpg new file mode 100644 index 000000000000..abdf847de93f Binary files /dev/null and b/docs/images/Ui_with_overview.jpg differ diff --git a/docs/images/a-shakra.jpg b/docs/images/a-shakra.jpg new file mode 100644 index 000000000000..c5424f363584 Binary files /dev/null and b/docs/images/a-shakra.jpg differ diff --git a/docs/images/change-d.png b/docs/images/change-d.png new file mode 100644 index 000000000000..dd53c1a00c38 Binary files /dev/null and b/docs/images/change-d.png differ diff --git a/docs/images/change-m.png b/docs/images/change-m.png new file mode 100644 index 000000000000..335beaedd1dd Binary files /dev/null and b/docs/images/change-m.png differ diff --git a/docs/images/change-w.png b/docs/images/change-w.png new file mode 100644 index 000000000000..0acdf900ac13 Binary files /dev/null and b/docs/images/change-w.png differ diff --git a/docs/images/change-y.png b/docs/images/change-y.png new file mode 100644 index 000000000000..78d4c90e89e3 Binary files /dev/null and b/docs/images/change-y.png differ diff --git a/docs/images/chochihtun.jpg b/docs/images/chochihtun.jpg new file mode 100644 index 000000000000..3f58629afdd5 Binary files /dev/null and b/docs/images/chochihtun.jpg differ diff --git a/docs/images/damithc.jpg b/docs/images/damithc.jpg deleted file mode 100644 index 127543883893..000000000000 Binary files a/docs/images/damithc.jpg and /dev/null differ diff --git a/docs/images/lejolly.jpg b/docs/images/lejolly.jpg deleted file mode 100644 index 2d1d94e0cf5d..000000000000 Binary files a/docs/images/lejolly.jpg and /dev/null differ diff --git a/docs/images/m133225.jpg b/docs/images/m133225.jpg deleted file mode 100644 index fd14fb94593a..000000000000 Binary files a/docs/images/m133225.jpg and /dev/null differ diff --git a/docs/images/raymond511.jpg b/docs/images/raymond511.jpg new file mode 100644 index 000000000000..085b046fe822 Binary files /dev/null and b/docs/images/raymond511.jpg differ diff --git a/docs/images/taskStorage.jpg b/docs/images/taskStorage.jpg new file mode 100644 index 000000000000..73cc8e39a80d Binary files /dev/null and b/docs/images/taskStorage.jpg differ diff --git a/docs/images/viewFiltered-sequence-diagram.jpeg b/docs/images/viewFiltered-sequence-diagram.jpeg new file mode 100644 index 000000000000..61ce8e2c4b93 Binary files /dev/null and b/docs/images/viewFiltered-sequence-diagram.jpeg differ diff --git a/docs/images/yijinl.jpg b/docs/images/yijinl.jpg deleted file mode 100644 index adbf62ad9406..000000000000 Binary files a/docs/images/yijinl.jpg and /dev/null differ diff --git a/docs/images/yl_coder.jpg b/docs/images/yl_coder.jpg deleted file mode 100644 index 17b48a732272..000000000000 Binary files a/docs/images/yl_coder.jpg and /dev/null differ diff --git a/docs/images/yungyung04.jpg b/docs/images/yungyung04.jpg new file mode 100644 index 000000000000..6ccc48ff5dd7 Binary files /dev/null and b/docs/images/yungyung04.jpg differ diff --git a/docs/team/ashakra.adoc b/docs/team/ashakra.adoc new file mode 100644 index 000000000000..302306c7ff5a --- /dev/null +++ b/docs/team/ashakra.adoc @@ -0,0 +1,52 @@ += Anas Shakra - Project Portfolio +:imagesDir: ../images +:stylesDir: ../stylesheets + +== PROJECT: Tuition Connect (TC) + +--- + +== Overview + +TuitionConnect is a desktop address book application used by private tuition teacher. + + +TuitionConnect aims to help the tutors to manage their busy schedule more efficiently and effectively. + + +TuitionConnect has many functionality catered to the tutors - adding and deleting a tutee, creating a schedule, rescheduling and many more. + + +The user interacts with it using a CLI, and it has a GUI created with JavaFX. It is written in Java, and has about 20 kLoC. + +== Summary of contributions + +* *Major enhancement*: Integrated *a new Task class into the application* +** What it does: The addressbook application is now able to interact, in various ways, with the newly created task object. In doing so, tutors are capable of generating and manipulating their various appointments at will. This enhancment inclues the ability to store the task object in the addressbook. +** Justification: This feature forms the foundation of the application as the app is based on a tutor's ability to create and interact with a task object in an addressbook environemnt. +** Highlights: This enhancement affects existing commands and commands to be added in future. It required an in-depth analysis of design alternatives. The implementation too was challenging as it required changes to almost every aspect of the codebase. +** Credits: While no outside code was used, it was useful to look over how the Person class interacted with the addressbook and see how that can be adjusted to allow the pre-existing codebase to accomodate the Task object. + +* *Minor enhancement*: added a listTask command that allows the user to view all tasks stored in the addressbook. + +* *Code contributed*: [https://github.com/CS2103JAN2018-T11-B1/main/blob/master/collated/functional/a-shakra.md[Functional code]] [https://github.com/CS2103JAN2018-T11-B1/main/blob/master/collated/test/a-shakra.md[Test code]] + +* *Other contributions*: + +** Project management: +*** Managed releases `v1.2` - `v1.4` (3 releases) on GitHub +** Enhancements to existing features: +*** Wrote additional tests for existing features (https://github.com[#68], https://github.com[#140]) +** Documentation: +*** Did cosmetic tweaks to existing contents of the User Guide: https://github.com[#68] +*** Updated Developer Guide to explain features: https://github.com[#68] +** Community: +*** PRs reviewed (with non-trivial review comments): https://github.com[#128] + + +== Contributions to the User Guide + +include::../UserGuide.adoc[tag=listTask] + +== Contributions to the Developer Guide + +include::../DeveloperGuide.adoc[tag=taskClass] + +include::../DeveloperGuide.adoc[tag=listCommand] diff --git a/docs/team/chochihtun.adoc b/docs/team/chochihtun.adoc new file mode 100644 index 000000000000..059d1e5e84c9 --- /dev/null +++ b/docs/team/chochihtun.adoc @@ -0,0 +1,50 @@ += Cho Chih Tun - Project Portfolio +:imagesDir: ../images +:stylesDir: ../stylesheets + +== PROJECT: Tuition Connect (TC) + +--- + +== Overview + +TuitionConnect is a desktop address book application used by private tuition teacher. +It aims to help the tutors to manage their busy schedule more efficiently and effectively. +The user interacts with it using a CLI, and it has a GUI created with JavaFX. It is written in Java, and has about 20 kLoC. + +== Summary of contributions + +* *Major enhancement*: integrated https://github.com/CS2103JAN2018-T11-B1/main/pull/95[*CalendarFX*] into the application +** What it does: has a calendar that reflects all the task entries and respond to all scheduling commands that the user inputs. +** Credits: [https://github.com/dlemmermann/CalendarFX[CalendarFX]] + +* *Minor enhancement*: +. Constructed classes such as PersonalTask, TuitionTask, Tutee, Subject, Grade, EducationLevel, School and relevant methods to support task and tutee management commands. +. Modified edit command to be able to edit tutee's details. +. Added addtutee command to add a new tutee into TuitionConnect application. https://github.com/CS2103JAN2018-T11-B1/main/pull/62[#62] +. Drafted a TuitionConnect UI theme and revamped the layout of the UI. + +* *Code contributed*: [https://github.com/CS2103JAN2018-T11-B1/main/blob/master/collated/functional/ChoChihTun.md[Functional code]] [https://github.com/CS2103JAN2018-T11-B1/main/blob/master/collated/test/ChoChihTun.md[Test code]] + +* *Other contributions*: + +*** Drafted and managed user guide in developer guide and on GitHub: https://github.com/CS2103JAN2018-T11-B1/main/pull/106[#106] +*** Responsible for issues and milestones on GitHub +*** Set up auto publishing of documentation on GitHub +*** Reported several bugs for other teams in class: (Example: [Endless Loop]https://github.com/CS2103JAN2018-W10-B3/main/issues/112) + +== Contributions to the User Guide + +include::../UserGuide.adoc[tag=introduction] + +include::../UserGuide.adoc[tag=addtutee] + +include::../UserGuide.adoc[tag=edit] + +include::../UserGuide.adoc[tag=change] + +include::../UserGuide.adoc[tag=ChoChihTunV2.0] + +== Contributions to the Developer Guide + +include::../DeveloperGuide.adoc[tag=addtutee] diff --git a/docs/team/ekabuyunglienadi.adoc b/docs/team/ekabuyunglienadi.adoc new file mode 100644 index 000000000000..f3bda3e37ab4 --- /dev/null +++ b/docs/team/ekabuyunglienadi.adoc @@ -0,0 +1,62 @@ += Eka Buyung Lienadi - Project Portfolio +:imagesDir: ../images +:stylesDir: ../stylesheets + +== PROJECT: Development of Address Book Level 4 : Tuition Connect + +--- + +== Overview + +TuitionConnect is an integration of address book and task manager optimized to be used by private tuition teacher for managing their busy schedule efficiently and effectively. + +== Summary of contributions + +* *Major enhancement*: added *scheduling features +** What it does: allows the users to organize their activities neatly by creating and deleting tasks. +Also, allows users to browse through their schedule with ease using the find and sort utilities. + +* *Minor enhancements* +** Implemented tutee listing, filtering and sorting features. + +* *Code contributed*: [https://github.com/CS2103JAN2018-T11-B1/main/blob/master/collated/functional/yungyung04.md[Functional code]] [https://github.com/CS2103JAN2018-T11-B1/main/blob/master/collated/test/yungyung04.md[Test code]] + +* *Other contributions*: + +** Project management: +*** Managed releases `v1.2` - `v1.4` (3 releases) on GitHub +*** Created the team organization in GitHub. +** Community: +*** PRs reviewed (with non-trivial review comments): (https://github.com/CS2103JAN2018-T11-B1/main[#60], https://github.com/CS2103JAN2018-T11-B1/main/pull/42[#42]) +*** Contributed to forum discussions (examples: https://github.com/CS2103JAN2018-T11-B1/main/pull/7[#7]) +** Tools: +*** Integrated a new Github plugin (TravisCI) to the team repo + +== Contributions to the User Guide + + +|=== +|_Given below are sections I contributed to the User Guide. They showcase my ability to write documentation targeting end-users._ +|=== + +include::../UserGuide.adoc[tag=ui_overview] + +include::../UserGuide.adoc[tag=conceptintro] + +include::../UserGuide.adoc[tag=addtuition] + +include::../UserGuide.adoc[tag=sorttask] + +== Contributions to the Developer Guide + +|=== +|_Given below are sections I contributed to the Developer Guide. They showcase my ability to write technical documentation and the technical depth of my contributions to the project._ +|=== + +include::../DeveloperGuide.adoc[tag=classDiagram] + +include::../DeveloperGuide.adoc[tag=storageDiagram] + +include::../DeveloperGuide.adoc[tag=sortpersoncommand] + +include::../DeveloperGuide.adoc[tag=natural] diff --git a/docs/team/johndoe.adoc b/docs/team/johndoe.adoc deleted file mode 100644 index 0dfa757e454b..000000000000 --- a/docs/team/johndoe.adoc +++ /dev/null @@ -1,71 +0,0 @@ -= John Doe - Project Portfolio -:imagesDir: ../images -:stylesDir: ../stylesheets - -== PROJECT: AddressBook - Level 4 - ---- - -== Overview - -AddressBook - Level 4 is a desktop address book application used for teaching Software Engineering principles. The user interacts with it using a CLI, and it has a GUI created with JavaFX. It is written in Java, and has about 10 kLoC. - -== Summary of contributions - -* *Major enhancement*: added *the ability to undo/redo previous commands* -** What it does: allows the user to undo all previous commands one at a time. Preceding undo commands can be reversed by using the redo command. -** Justification: This feature improves the product significantly because a user can make mistakes in commands and the app should provide a convenient way to rectify them. -** Highlights: This enhancement affects existing commands and commands to be added in future. It required an in-depth analysis of design alternatives. The implementation too was challenging as it required changes to existing commands. -** Credits: _{mention here if you reused any code/ideas from elsewhere or if a third-party library is heavily used in the feature so that a reader can make a more accurate judgement of how much effort went into the feature}_ - -* *Minor enhancement*: added a history command that allows the user to navigate to previous commands using up/down keys. - -* *Code contributed*: [https://github.com[Functional code]] [https://github.com[Test code]] _{give links to collated code files}_ - -* *Other contributions*: - -** Project management: -*** Managed releases `v1.3` - `v1.5rc` (3 releases) on GitHub -** Enhancements to existing features: -*** Updated the GUI color scheme (Pull requests https://github.com[#33], https://github.com[#34]) -*** Wrote additional tests for existing features to increase coverage from 88% to 92% (Pull requests https://github.com[#36], https://github.com[#38]) -** Documentation: -*** Did cosmetic tweaks to existing contents of the User Guide: https://github.com[#14] -** Community: -*** PRs reviewed (with non-trivial review comments): https://github.com[#12], https://github.com[#32], https://github.com[#19], https://github.com[#42] -*** Contributed to forum discussions (examples: https://github.com[1], https://github.com[2], https://github.com[3], https://github.com[4]) -*** Reported bugs and suggestions for other teams in the class (examples: https://github.com[1], https://github.com[2], https://github.com[3]) -*** Some parts of the history feature I added was adopted by several other class mates (https://github.com[1], https://github.com[2]) -** Tools: -*** Integrated a third party library (Natty) to the project (https://github.com[#42]) -*** Integrated a new Github plugin (CircleCI) to the team repo - -_{you can add/remove categories in the list above}_ - -== Contributions to the User Guide - - -|=== -|_Given below are sections I contributed to the User Guide. They showcase my ability to write documentation targeting end-users._ -|=== - -include::../UserGuide.adoc[tag=undoredo] - -include::../UserGuide.adoc[tag=dataencryption] - -== Contributions to the Developer Guide - -|=== -|_Given below are sections I contributed to the Developer Guide. They showcase my ability to write technical documentation and the technical depth of my contributions to the project._ -|=== - -include::../DeveloperGuide.adoc[tag=undoredo] - -include::../DeveloperGuide.adoc[tag=dataencryption] - - -== PROJECT: PowerPointLabs - ---- - -_{Optionally, you may include other projects in your portfolio.}_ diff --git a/docs/team/raymond511.adoc b/docs/team/raymond511.adoc new file mode 100644 index 000000000000..9f4c5e5f79a3 --- /dev/null +++ b/docs/team/raymond511.adoc @@ -0,0 +1,39 @@ += Raymond Zheng - Project Portfolio +:imagesDir: ../images +:stylesDir: ../stylesheets + +== PROJECT: Tuition Connect (TC) + +--- + +== Overview + +TuitionConnect is a desktop address book application used by private tuition teacher. + + +TuitionConnect aims to help the tutors to manage their busy schedule more efficiently and effectively. + + +TuitionConnect has many functionality catered to the tutors - adding and deleting a tutee, creating a schedule, rescheduling and many more. + + +The user interacts with it using a CLI, and it has a GUI created with JavaFX. It is written in Java, and has about 20 kLoC. + +== Summary of contributions + +* *Major enhancement*: Encryption +** What it does: The addressbook application is now able to automatically encrypt any changes made to the address book. +* *Minor enhancement*: The addressbook application is now able to automatically backup and saves any changes made. + +* *Code contributed*: [https://github.com/CS2103JAN2018-T11-B1/main/blob/master/collated/functional/raymond511.md[Functional code]] [https://github.com/CS2103JAN2018-T11-B1/main/blob/master/collated/test/raymond511.md[Test code]] + +* *Other contributions*: + +** Project management: +*** Managed releases `v1.2` - `v1.4` (3 releases) on GitHub +** Enhancements to existing features: +** Documentation: +*** Updated existing contents of the User Guide to explain encryption. + + + +== Contributions to the User Guide + +include::../UserGuide.adoc[tag=encryption] diff --git a/gradle.properties b/gradle.properties index 728342109ca9..a269ca83acce 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,3 +1,4 @@ org.gradle.daemon=true org.gradle.parallel=false org.gradle.jvmargs=-XX:MaxPermSize=512m -XX:+CMSClassUnloadingEnabled -XX:+CMSPermGenSweepingEnabled -XX:+HeapDumpOnOutOfMemoryError -Xmx1024m -Dfile.encoding=utf-8 + diff --git a/lib/README-EXT.txt b/lib/README-EXT.txt new file mode 100644 index 000000000000..6077947eddc1 --- /dev/null +++ b/lib/README-EXT.txt @@ -0,0 +1,18 @@ +This directory contains third-party jar files that are required +by CalendarFX. The framework can not work without these. + +- controlsfx-xxx.jar + + Custom controls developed as part of the open source project ControlsFX. + +- fontawesomefx-commons-xxx.jar + + Common support code for web fonts in JavaFX. + +- fontawesomefx-fontawesome-xxx.jar + + The fontawesome font for JavaFX. + +- license4j-1.4.0.jar + + Support for licensing keys. diff --git a/lib/calendarfx-recurrence-8.4.0.jar b/lib/calendarfx-recurrence-8.4.0.jar new file mode 100644 index 000000000000..db3b42027fe7 Binary files /dev/null and b/lib/calendarfx-recurrence-8.4.0.jar differ diff --git a/lib/calendarfx-view-8.4.0.jar b/lib/calendarfx-view-8.4.0.jar new file mode 100644 index 000000000000..af91d43dc93c Binary files /dev/null and b/lib/calendarfx-view-8.4.0.jar differ diff --git a/lib/controlsfx-8.40.11.jar b/lib/controlsfx-8.40.11.jar new file mode 100644 index 000000000000..3e409877f818 Binary files /dev/null and b/lib/controlsfx-8.40.11.jar differ diff --git a/lib/fontawesomefx-commons-8.13.jar b/lib/fontawesomefx-commons-8.13.jar new file mode 100644 index 000000000000..5acff3236bf2 Binary files /dev/null and b/lib/fontawesomefx-commons-8.13.jar differ diff --git a/lib/fontawesomefx-fontawesome-4.7.0-1.jar b/lib/fontawesomefx-fontawesome-4.7.0-1.jar new file mode 100644 index 000000000000..8360e5db9fe5 Binary files /dev/null and b/lib/fontawesomefx-fontawesome-4.7.0-1.jar differ diff --git a/lib/license4j-1.4.0.jar b/lib/license4j-1.4.0.jar new file mode 100644 index 000000000000..ee72d35c53c3 Binary files /dev/null and b/lib/license4j-1.4.0.jar differ diff --git a/src/main/java/seedu/address/MainApp.java b/src/main/java/seedu/address/MainApp.java index fa0800d55cb9..c4a7dcecbf75 100644 --- a/src/main/java/seedu/address/MainApp.java +++ b/src/main/java/seedu/address/MainApp.java @@ -32,6 +32,7 @@ import seedu.address.storage.StorageManager; import seedu.address.storage.UserPrefsStorage; import seedu.address.storage.XmlAddressBookStorage; +import seedu.address.ui.CalendarPanel; import seedu.address.ui.Ui; import seedu.address.ui.UiManager; @@ -101,7 +102,7 @@ private Model initModelManager(Storage storage, UserPrefs userPrefs) { logger.warning("Problem while reading from the file. Will be starting with an empty AddressBook"); initialData = new AddressBook(); } - + CalendarPanel.updateCalendar(initialData.getTaskList()); return new ModelManager(initialData, userPrefs); } diff --git a/src/main/java/seedu/address/commons/core/Messages.java b/src/main/java/seedu/address/commons/core/Messages.java index 1deb3a1e4695..dac510a35de1 100644 --- a/src/main/java/seedu/address/commons/core/Messages.java +++ b/src/main/java/seedu/address/commons/core/Messages.java @@ -7,7 +7,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_INPUT_TYPES = "The input type specified is invalid \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_TUTEE_INDEX = "The person index provided does not refer to a tutee"; + public static final String MESSAGE_INVALID_FILTER_CATEGORY = "The filter category specified is invalid \n%1$s"; + public static final String MESSAGE_INVALID_SORTER_CATEGORY = "The sort category specified is invalid \n%1$s"; + public static final String MESSAGE_INVALID_DATE_TIME = "The input date and time is invalid. " + + "It should be in the form of dd/mm/yyyy hh:mm"; + public static final String MESSAGE_INVALID_KEYWORD_GIVEN = "The given keywords are invalid"; + public static final String MESSAGE_INVALID_DURATION = "The duration format is invalid. " + + "It should be in the form of XXh:XXm and it cannot be zero"; public static final String MESSAGE_PERSONS_LISTED_OVERVIEW = "%1$d persons listed!"; - + public static final String MESSAGE_TASKS_LISTED_OVERVIEW = "%1$d tasks listed!"; + public static final String MESSAGE_INVALID_MONTH_RANGE_FORMAT = "The keywords are invalid. 2 different months " + + "should be referred when 'between' is chosen as the input type."; + public static final String MESSAGE_TASK_TIMING_CLASHES = "This task clashes with another task"; } diff --git a/src/main/java/seedu/address/commons/events/model/AddressBookChangedEvent.java b/src/main/java/seedu/address/commons/events/model/AddressBookChangedEvent.java index 7db9b5c48ed6..dc877d09a6d2 100644 --- a/src/main/java/seedu/address/commons/events/model/AddressBookChangedEvent.java +++ b/src/main/java/seedu/address/commons/events/model/AddressBookChangedEvent.java @@ -14,6 +14,7 @@ public AddressBookChangedEvent(ReadOnlyAddressBook data) { @Override public String toString() { - return "number of persons " + data.getPersonList().size() + ", number of tags " + data.getTagList().size(); + return "number of persons " + data.getPersonList().size() + ", number of tags " + data.getTagList().size() + + "number of tasks" + data.getTaskList().size(); } } diff --git a/src/main/java/seedu/address/commons/events/ui/TaskPanelSelectionChangedEvent.java b/src/main/java/seedu/address/commons/events/ui/TaskPanelSelectionChangedEvent.java new file mode 100644 index 000000000000..bfec5b79833a --- /dev/null +++ b/src/main/java/seedu/address/commons/events/ui/TaskPanelSelectionChangedEvent.java @@ -0,0 +1,26 @@ +package seedu.address.commons.events.ui; + +import seedu.address.commons.events.BaseEvent; +import seedu.address.ui.TaskCard; + +/** + * Represents a selection change in the Person List Panel + */ +public class TaskPanelSelectionChangedEvent extends BaseEvent { + + private final TaskCard newSelection; + + public TaskPanelSelectionChangedEvent(TaskCard newSelection) { + this.newSelection = newSelection; + } + + @Override + public String toString() { + return this.getClass().getSimpleName(); + } + + public TaskCard getNewSelection() { + return newSelection; + } + +} diff --git a/src/main/java/seedu/address/commons/util/EncryptionUtil.java b/src/main/java/seedu/address/commons/util/EncryptionUtil.java new file mode 100644 index 000000000000..0156e1f98070 --- /dev/null +++ b/src/main/java/seedu/address/commons/util/EncryptionUtil.java @@ -0,0 +1,187 @@ +package seedu.address.commons.util; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.security.GeneralSecurityException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.security.SecureRandom; +import java.util.logging.Logger; +import javax.crypto.BadPaddingException; +import javax.crypto.Cipher; + +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.SecretKey; + +import javax.crypto.spec.SecretKeySpec; + +import seedu.address.commons.core.LogsCenter; + +/** + * A Class that encrypts and decrypts XML files stored on the hard disk. + * + */ +//@@author raymond511 +public class EncryptionUtil { + + /** + *The standard version of the JRE/JDK are under export restrictions. + *That also includes that some cryptographic algorithms are not allowed to be shipped in the standard version. + *Replace files in library with Java Cryptography Extension (JCE) Unlimited Strength Jurisdiction Policy Files 8 + */ + + private static final Logger logger = LogsCenter.getLogger(EncryptionUtil.class); + private static final String passwordToHash = "password"; + private static byte[] salt = new byte[0]; + private static final String password = getSecurePassword(passwordToHash, salt); + + /** + * Adds salt to password cryptography + * @throws NoSuchAlgorithmException if salt acnnot be generated + * @throws NoSuchProviderException if salt cannot be generated + */ + + private static byte[] getSalt() throws NoSuchAlgorithmException, NoSuchProviderException { + try { + SecureRandom sr = SecureRandom.getInstance("SHA1PRNG", "SUN"); + byte[] salt = new byte[16]; + sr.nextBytes(salt); + } catch (NoSuchAlgorithmException e) { + logger.severe("This algorithm is not supported " + e.getMessage()); + } catch (NoSuchProviderException e) { + logger.severe("The provider is not available " + e.getMessage()); + } + return salt; + } + + /** + * Generates a secure password + * + * @param passwordToHash used to generate a new password + * @param salt to adds security to the new password + * @throws NoSuchAlgorithmException if new password cannot be generated + */ + + private static String getSecurePassword(String passwordToHash, byte[] salt) { + String generatedPassword = null; + try { + MessageDigest md = MessageDigest.getInstance("MD5"); + md.update(salt); + byte[] bytes = md.digest(passwordToHash.getBytes()); + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < bytes.length; i++) { + sb.append(Integer.toString((bytes[i] & 0xff) + 0x100, 16).substring(1)); + } + generatedPassword = sb.toString(); + } catch (NoSuchAlgorithmException e) { + logger.severe("This algorithm is not supported " + e.getMessage()); + } + return generatedPassword; + } + + /** + * Encrypts an XML file. + * + * @param file path of the file to be encrypted + * @throws IOException if file could not be found + */ + public static void encrypt(File file) throws IOException { + try { + Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding"); + SecretKey privateKey = generateKey(); + cipher.init(Cipher.ENCRYPT_MODE, privateKey); + fileToBytes(cipher, file); + } catch (GeneralSecurityException gse) { + logger.severe("Cipher or Padding might not be supported " + gse.getMessage()); + } catch (UnsupportedEncodingException use) { + logger.info("Encoding Unsupported " + use.getMessage()); + } + + } + + /** + * Decrypts XML file + * + * @param file path of the file to be decrypted + * @throws IOException if file could not be found + */ + public static void decrypt(File file) throws IOException { + + try { + Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding"); + SecretKey privateKey = generateKey(); + cipher.init(Cipher.DECRYPT_MODE, privateKey); + fileToBytes(cipher, file); + } catch (GeneralSecurityException gse) { + logger.severe("Cipher or Padding might not be supported " + gse.getMessage()); + } catch (UnsupportedEncodingException use) { + logger.info("Encoding Unsupported " + use.getMessage()); + } + } + + /** + * Processes the given file using the given cipher + * + * @param cipher cipher used for encryption or decryption + * @param file path of the file to be encrypted or decrypted + * @throws IOException if file could not be found + */ + + private static void fileToBytes(Cipher cipher, File file) throws IOException { + + FileInputStream fileInputStream = null; + FileOutputStream fileOutputStream = null; + try { + fileInputStream = new FileInputStream(file); + byte[] readBytes = new byte[(int) file.length()]; + fileInputStream.read(readBytes); + + byte[] writeBytes = cipher.doFinal(readBytes); + fileOutputStream = new FileOutputStream(file); + fileOutputStream.write(writeBytes); + + } catch (BadPaddingException be) { + logger.info("File might not decoded/encoded properly due to bad padding " + be.getMessage()); + } catch (IllegalBlockSizeException ibe) { + logger.info("Input length size must be in multiple of 16 " + ibe.getMessage()); + } finally { + try { + if (fileInputStream != null) { + fileInputStream.close(); + } + if (fileOutputStream != null) { + fileOutputStream.close(); + } + } catch (IOException ioe) { + logger.info("File streams could not be closed " + ioe.getMessage()); + } + } + } + + /** + * Method to generate a SecretKey using the password provided + * + * @return SecretKey generated using AES encryption + */ + public static SecretKey generateKey() { + SecretKeySpec secretKeySpec = null; + try { + salt = getSalt(); + MessageDigest digester = MessageDigest.getInstance("SHA-256"); + digester.update(password.getBytes("UTF-8")); + byte[] key = digester.digest(); + secretKeySpec = new SecretKeySpec(key , 0 , 16 , "AES"); + } catch (NoSuchAlgorithmException nae) { + logger.info("Algorithm Unsupported " + nae.getMessage()); + } catch (UnsupportedEncodingException use) { + logger.info("Encoding Unsupported " + use.getMessage()); + } catch (NoSuchProviderException e) { + logger.severe("The provider is not available " + e.getMessage()); + } + return secretKeySpec; + } +} diff --git a/src/main/java/seedu/address/logic/Logic.java b/src/main/java/seedu/address/logic/Logic.java index 8b34b862039a..8578c5519bb1 100644 --- a/src/main/java/seedu/address/logic/Logic.java +++ b/src/main/java/seedu/address/logic/Logic.java @@ -4,6 +4,7 @@ import seedu.address.logic.commands.CommandResult; import seedu.address.logic.commands.exceptions.CommandException; import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.Task; import seedu.address.model.person.Person; /** @@ -22,6 +23,8 @@ public interface Logic { /** Returns an unmodifiable view of the filtered list of persons */ ObservableList getFilteredPersonList(); + ObservableList getFilteredTaskList(); + /** Returns the list of input entered by the user, encapsulated in a {@code ListElementPointer} object */ ListElementPointer getHistorySnapshot(); } diff --git a/src/main/java/seedu/address/logic/LogicManager.java b/src/main/java/seedu/address/logic/LogicManager.java index 9f6846bdfc74..d64181f7b52b 100644 --- a/src/main/java/seedu/address/logic/LogicManager.java +++ b/src/main/java/seedu/address/logic/LogicManager.java @@ -11,7 +11,9 @@ import seedu.address.logic.parser.AddressBookParser; import seedu.address.logic.parser.exceptions.ParseException; import seedu.address.model.Model; +import seedu.address.model.Task; import seedu.address.model.person.Person; +import seedu.address.ui.CalendarPanel; /** * The main LogicManager of the app. @@ -39,6 +41,7 @@ public CommandResult execute(String commandText) throws CommandException, ParseE command.setData(model, history, undoRedoStack); CommandResult result = command.execute(); undoRedoStack.push(command); + CalendarPanel.updateCalendar(model.getFilteredTaskList()); return result; } finally { history.add(commandText); @@ -50,6 +53,12 @@ public ObservableList getFilteredPersonList() { return model.getFilteredPersonList(); } + //@@author a-shakra + @Override + public ObservableList getFilteredTaskList() { + return model.getFilteredTaskList(); + } + //@@author @Override public ListElementPointer getHistorySnapshot() { return new ListElementPointer(history.getHistory()); diff --git a/src/main/java/seedu/address/logic/commands/AddCommand.java b/src/main/java/seedu/address/logic/commands/AddCommand.java index c334710c0ea3..d5de4a572715 100644 --- a/src/main/java/seedu/address/logic/commands/AddCommand.java +++ b/src/main/java/seedu/address/logic/commands/AddCommand.java @@ -17,6 +17,7 @@ public class AddCommand extends UndoableCommand { public static final String COMMAND_WORD = "add"; + public static final String COMMAND_ALIAS = "a"; public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds a person to the address book. " + "Parameters: " diff --git a/src/main/java/seedu/address/logic/commands/AddPersonalTaskCommand.java b/src/main/java/seedu/address/logic/commands/AddPersonalTaskCommand.java new file mode 100644 index 000000000000..9ece44e4e31b --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/AddPersonalTaskCommand.java @@ -0,0 +1,57 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; + +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.personal.PersonalTask; +import seedu.address.model.task.exceptions.TimingClashException; + +//@@author yungyung04 +/** + * Adds a personal task into the schedule. + */ +public class AddPersonalTaskCommand extends UndoableCommand { + + public static final String COMMAND_WORD = "addtask"; + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds a personal task into the schedule.\n" + + "Parameters: " + + "Date(dd/mm/yyyy) " + + "Start time(hh:mm) " + + "Duration(XXhXXm) " + + "Description( anything; leading and trailing whitespaces will be trimmed )\n" + + "Example: " + COMMAND_WORD + " " + + "10/12/2018 " + + "12:30 " + + "1h30m " + + "Yoga"; + public static final String MESSAGE_SUCCESS = "Task added: %1$s"; + + private final PersonalTask toAdd; + + /** + * Creates an AddPersonalTaskCommand to add the specified {@code Task}. + */ + public AddPersonalTaskCommand(PersonalTask task) { + requireNonNull(task); + toAdd = task; + } + + //@@author ChoChihTun + @Override + public CommandResult executeUndoableCommand() throws CommandException { + try { + model.addTask(toAdd); + } catch (TimingClashException tce) { + throw new CommandException(tce.getMessage()); + } + return new CommandResult(String.format(MESSAGE_SUCCESS, toAdd)); + } + + //@@author yungyung04 + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof AddPersonalTaskCommand // instanceof handles nulls + && toAdd.equals(((AddPersonalTaskCommand) other).toAdd)); + } +} diff --git a/src/main/java/seedu/address/logic/commands/AddTuitionTaskCommand.java b/src/main/java/seedu/address/logic/commands/AddTuitionTaskCommand.java new file mode 100644 index 000000000000..17948fc060ed --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/AddTuitionTaskCommand.java @@ -0,0 +1,109 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; + +import java.time.LocalDateTime; +import java.util.List; + +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; + +import seedu.address.logic.commands.exceptions.CommandException; + +import seedu.address.model.person.Person; +import seedu.address.model.task.exceptions.TimingClashException; +import seedu.address.model.tutee.TuitionTask; +import seedu.address.model.tutee.Tutee; + +//@@author yungyung04 + +/** + * Adds a tuition (task) into the schedule. + */ +public class AddTuitionTaskCommand extends UndoableCommand { + + public static final String COMMAND_WORD = "addtuition"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds a tuition (task) into the schedule.\n" + + "Parameters: " + + "tutee_index" + + "Date(dd/mm/yyyy) " + + "Start time(hh:mm) " + + "Duration(XXhXXm) " + + "Description( anything; leading and trailing whitespaces will be trimmed )\n" + + "Example: " + COMMAND_WORD + " " + + "1 " + + "10/12/2018 " + + "12:30 " + + "1h30m " + + "Calculus homework page 24"; + + public static final String MESSAGE_SUCCESS = "New tuition task added."; + + private final Index targetIndex; + private final LocalDateTime taskdateTime; + private final String duration; + private final String description; + + private TuitionTask toAdd; + private String associatedTutee; + + /** + * Creates an AddTuition to add the specified {@code Task} which is associated to {@code Tutee}. + */ + public AddTuitionTaskCommand(Index targetIndex, LocalDateTime taskDateTime, String duration, String description) { + requireNonNull(taskDateTime); + requireNonNull(duration); + requireNonNull(description); + this.targetIndex = targetIndex; + this.taskdateTime = taskDateTime; + this.duration = duration; + this.description = description; + } + + //@@author ChoChihTun + @Override + public CommandResult executeUndoableCommand() throws CommandException { + try { + model.addTask(toAdd); + } catch (TimingClashException tce) { + throw new CommandException(tce.getMessage()); + } + return new CommandResult(MESSAGE_SUCCESS); + } + + //@@author yungyung04 + @Override + protected void preprocessUndoableCommand() throws CommandException { + associatedTutee = getAssociatedTutee().getName().fullName; + toAdd = new TuitionTask(associatedTutee, taskdateTime, duration, description); + } + + /** + * Returns the {@code Tutee} object that is pointed by the index as shown in the last displayed conatct list. + */ + private Tutee getAssociatedTutee() throws CommandException { + List lastShownList = model.getFilteredPersonList(); + + if (targetIndex.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + } + requireNonNull(lastShownList.get(targetIndex.getZeroBased())); + Person associatedPerson = lastShownList.get(targetIndex.getZeroBased()); + if (!(associatedPerson instanceof Tutee)) { + throw new CommandException(Messages.MESSAGE_INVALID_TUTEE_INDEX); + } + return (Tutee) lastShownList.get(targetIndex.getZeroBased()); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof AddTuitionTaskCommand // instanceof handles nulls + && targetIndex.equals(((AddTuitionTaskCommand) other).targetIndex)) + && taskdateTime.equals(((AddTuitionTaskCommand) other).taskdateTime) + && duration.equals(((AddTuitionTaskCommand) other).duration) + && description.equals(((AddTuitionTaskCommand) other).description); + + } +} diff --git a/src/main/java/seedu/address/logic/commands/AddTuteeCommand.java b/src/main/java/seedu/address/logic/commands/AddTuteeCommand.java new file mode 100644 index 000000000000..70c5b13c5c4b --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/AddTuteeCommand.java @@ -0,0 +1,80 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_EDUCATION_LEVEL; +import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; +import static seedu.address.logic.parser.CliSyntax.PREFIX_GRADE; +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_SCHOOL; +import static seedu.address.logic.parser.CliSyntax.PREFIX_SUBJECT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; + +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.person.exceptions.DuplicatePersonException; +import seedu.address.model.tutee.Tutee; + +//@@author ChoChihTun +/** + * Adds a tutee to the address book + */ +public class AddTuteeCommand extends UndoableCommand { + public static final String COMMAND_WORD = "addtutee"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds a tutee to the address book. " + + "Parameters: " + + PREFIX_NAME + "NAME " + + PREFIX_PHONE + "PHONE " + + PREFIX_EMAIL + "EMAIL " + + PREFIX_ADDRESS + "ADDRESS " + + PREFIX_SUBJECT + "SUBJECT " + + PREFIX_GRADE + "GRADE " + + PREFIX_EDUCATION_LEVEL + "EDUCATION LEVEL " + + PREFIX_SCHOOL + "SCHOOL " + + "[" + PREFIX_TAG + "TAG]...\n" + + "Example: " + COMMAND_WORD + " " + + PREFIX_NAME + "John Doe " + + PREFIX_PHONE + "98765432 " + + PREFIX_EMAIL + "johnd@example.com " + + PREFIX_ADDRESS + "311, Clementi Ave 2, #02-25 " + + PREFIX_SUBJECT + "Economics " + + PREFIX_GRADE + "B+ " + + PREFIX_EDUCATION_LEVEL + "junior college " + + PREFIX_SCHOOL + "Victoria Junior College " + + PREFIX_TAG + "priority " + + PREFIX_TAG + "owesMoney"; + + public static final String MESSAGE_SUCCESS = "New tutee added: %1$s"; + public static final String MESSAGE_DUPLICATE_PERSON = "This person already exists in the address book"; + + private final Tutee toAdd; + + /** + * Creates an AddTuteeCommand to add the specified {@code Tutee} + */ + public AddTuteeCommand(Tutee tutee) { + requireNonNull(tutee); + toAdd = tutee; + } + + @Override + public CommandResult executeUndoableCommand() throws CommandException { + requireNonNull(model); + try { + model.addPerson(toAdd); + return new CommandResult(String.format(MESSAGE_SUCCESS, toAdd)); + } catch (DuplicatePersonException dpe) { + throw new CommandException(MESSAGE_DUPLICATE_PERSON); + } + + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof AddTuteeCommand // instanceof handles nulls + && toAdd.equals(((AddTuteeCommand) other).toAdd)); + } +} + diff --git a/src/main/java/seedu/address/logic/commands/ChangeCommand.java b/src/main/java/seedu/address/logic/commands/ChangeCommand.java new file mode 100644 index 000000000000..ab5d2d4fab1c --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/ChangeCommand.java @@ -0,0 +1,54 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; + +import seedu.address.ui.CalendarPanel; + +//@@author ChoChihTun +/** + * Changes the Calendar Time Unit View of the Application + */ +public class ChangeCommand extends Command { + public static final String COMMAND_WORD = "change"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Changes the calendar view " + + "Parameters: " + + "TIME_UNIT (Only d, w, m or y) " + + "Example: " + COMMAND_WORD + " d"; + + public static final String MESSAGE_CONSTRAINT = "Time unit can only be d, w, m or y for day, week, month and year" + + " respectively"; + + public static final String MESSAGE_SUCCESS = "Calendar view changed"; + public static final String MESSAGE_SAME_VIEW = "Calendar is already in the requested view"; + private static final int INDEX_OF_TIME_UNIT = 0; + private static final String INITIAL_TIME_UNIT = "d"; + + private static String timeUnit = INITIAL_TIME_UNIT; + + /** + * Creates an ChangeCommand to change calendar into the specified view page time unit {@code timeUnit} + */ + public ChangeCommand(String timeUnit) { + requireNonNull(timeUnit); + this.timeUnit = timeUnit; + } + + public static String getTimeUnit() { + return timeUnit; + } + + @Override + public CommandResult execute() { + CalendarPanel.changeViewPage(timeUnit.charAt(INDEX_OF_TIME_UNIT)); + return new CommandResult(String.format(MESSAGE_SUCCESS)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof ChangeCommand // instanceof handles nulls + && timeUnit.equals(((ChangeCommand) other).timeUnit)); + } + +} diff --git a/src/main/java/seedu/address/logic/commands/ClearCommand.java b/src/main/java/seedu/address/logic/commands/ClearCommand.java index ceeb7ba913c6..6d04efaf3295 100644 --- a/src/main/java/seedu/address/logic/commands/ClearCommand.java +++ b/src/main/java/seedu/address/logic/commands/ClearCommand.java @@ -10,6 +10,7 @@ public class ClearCommand extends UndoableCommand { public static final String COMMAND_WORD = "clear"; + public static final String COMMAND_ALIAS = "c"; public static final String MESSAGE_SUCCESS = "Address book has been cleared!"; diff --git a/src/main/java/seedu/address/logic/commands/Command.java b/src/main/java/seedu/address/logic/commands/Command.java index 6580e0b51c90..4f2fcc3edf6e 100644 --- a/src/main/java/seedu/address/logic/commands/Command.java +++ b/src/main/java/seedu/address/logic/commands/Command.java @@ -24,6 +24,16 @@ public static String getMessageForPersonListShownSummary(int displaySize) { return String.format(Messages.MESSAGE_PERSONS_LISTED_OVERVIEW, displaySize); } + /** + * Constructs a feedback message to summarise an operation that displayed a listing of tasks. + * + * @param displaySize used to generate summary + * @return summary message for tasks displayed + */ + public static String getMessageForTaskListShownSummary(int displaySize) { + return String.format(Messages.MESSAGE_TASKS_LISTED_OVERVIEW, displaySize); + } + /** * Executes the command and returns the result message. * diff --git a/src/main/java/seedu/address/logic/commands/DeleteCommand.java b/src/main/java/seedu/address/logic/commands/DeleteCommand.java index b539d240001a..0cd0fbd860ad 100644 --- a/src/main/java/seedu/address/logic/commands/DeleteCommand.java +++ b/src/main/java/seedu/address/logic/commands/DeleteCommand.java @@ -17,6 +17,7 @@ public class DeleteCommand extends UndoableCommand { public static final String COMMAND_WORD = "delete"; + public static final String COMMAND_ALIAS = "d"; public static final String MESSAGE_USAGE = COMMAND_WORD + ": Deletes the person identified by the index number used in the last person listing.\n" 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 000000000000..3c5420e91d8e --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/DeleteTaskCommand.java @@ -0,0 +1,72 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; + +import java.util.List; +import java.util.Objects; + +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; + +import seedu.address.logic.commands.exceptions.CommandException; + +import seedu.address.model.Task; +import seedu.address.model.task.exceptions.TaskNotFoundException; + +//@@author yungyung04 +/** + * Deletes a task from the schedule. + */ +public class DeleteTaskCommand extends UndoableCommand { + + public static final String COMMAND_WORD = "deletetask"; + + public static final String MESSAGE_USAGE = + COMMAND_WORD + ": Deletes a tuition or personal task from the schedule.\n" + + "Parameters: " + + "index of Task" + + "Example: " + COMMAND_WORD + " 1"; + + public static final String MESSAGE_SUCCESS = "Deleted task : %1$s"; + + private final Index targetIndex; + private Task toDelete; + + public DeleteTaskCommand(Index indexOfTask) { + targetIndex = indexOfTask; + } + + @Override + public CommandResult executeUndoableCommand() { + requireNonNull(toDelete); + try { + model.deleteTask(toDelete); + } catch (TaskNotFoundException tnfe) { + throw new AssertionError("The target person cannot be missing"); + } + return new CommandResult(String.format(MESSAGE_SUCCESS, toDelete.toString())); + } + + @Override + protected void preprocessUndoableCommand() throws CommandException { + toDelete = getAssociatedTask(); + } + + private Task getAssociatedTask() throws CommandException { + List lastShownTaskList = model.getFilteredTaskList(); + + if (targetIndex.getZeroBased() >= lastShownTaskList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_TASK_DISPLAYED_INDEX); + } + + return lastShownTaskList.get(targetIndex.getZeroBased()); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof DeleteTaskCommand // instanceof handles nulls + && targetIndex.equals(((DeleteTaskCommand) other).targetIndex)) + && Objects.equals(this.toDelete, ((DeleteTaskCommand) other).toDelete); + } +} diff --git a/src/main/java/seedu/address/logic/commands/EditCommand.java b/src/main/java/seedu/address/logic/commands/EditCommand.java index e6c3a3e034bc..6b7c903430e2 100644 --- a/src/main/java/seedu/address/logic/commands/EditCommand.java +++ b/src/main/java/seedu/address/logic/commands/EditCommand.java @@ -27,6 +27,11 @@ import seedu.address.model.person.exceptions.DuplicatePersonException; import seedu.address.model.person.exceptions.PersonNotFoundException; import seedu.address.model.tag.Tag; +import seedu.address.model.tutee.EducationLevel; +import seedu.address.model.tutee.Grade; +import seedu.address.model.tutee.School; +import seedu.address.model.tutee.Subject; +import seedu.address.model.tutee.Tutee; /** * Edits the details of an existing person in the address book. @@ -34,6 +39,7 @@ public class EditCommand extends UndoableCommand { public static final String COMMAND_WORD = "edit"; + public static final String COMMAND_ALIAS = "e"; public static final String MESSAGE_USAGE = COMMAND_WORD + ": Edits the details of the person identified " + "by the index number used in the last person listing. " @@ -51,7 +57,9 @@ public class EditCommand extends UndoableCommand { public static final String MESSAGE_EDIT_PERSON_SUCCESS = "Edited Person: %1$s"; public static final String MESSAGE_NOT_EDITED = "At least one field to edit must be provided."; public static final String MESSAGE_DUPLICATE_PERSON = "This person already exists in the address book."; + public static final String MESSAGE_INVALID_PERSON_TO_EDIT = "This person is not a tutee."; + private static final String TUTEE_TAG_NAME = "Tutee"; private final Index index; private final EditPersonDescriptor editPersonDescriptor; @@ -92,9 +100,40 @@ protected void preprocessUndoableCommand() throws CommandException { } personToEdit = lastShownList.get(index.getZeroBased()); + if (!(personToEdit instanceof Tutee) && !isEditPersonFieldValid()) { + throw new CommandException(MESSAGE_INVALID_PERSON_TO_EDIT); + } editedPerson = createEditedPerson(personToEdit, editPersonDescriptor); } + //@@author ChoChihTun + /** + * Checks if fields to be edited is valid for a person object + * + * @return true if fields to edit are valid for a person object + * false if fields to edit are invalid for a person object + */ + private boolean isEditPersonFieldValid() { + Tag tuteeTag = new Tag(TUTEE_TAG_NAME); + return !editPersonDescriptor.isAnyTuteeFieldEdited() + && isEditedPersonTagValid(tuteeTag); + } + + /** + * Checks if edited tag for person object is valid + * + * @param tuteeTag tutee tag is invalid for a person object + * @return true if edited tag is valid or tag is not being edited + * false if new tag is a tutee tag which is invalid for person + */ + private boolean isEditedPersonTagValid(Tag tuteeTag) { + if (editPersonDescriptor.isTagEdited()) { + return !editPersonDescriptor.tags.contains(tuteeTag); + } + return true; + } + //@@author + /** * Creates and returns a {@code Person} with the details of {@code personToEdit} * edited with {@code editPersonDescriptor}. @@ -108,6 +147,18 @@ private static Person createEditedPerson(Person personToEdit, EditPersonDescript Address updatedAddress = editPersonDescriptor.getAddress().orElse(personToEdit.getAddress()); Set updatedTags = editPersonDescriptor.getTags().orElse(personToEdit.getTags()); + //@@author ChoChihTun + if (personToEdit instanceof Tutee) { + Subject updatedSubject = editPersonDescriptor.getSubject().orElse(((Tutee) personToEdit).getSubject()); + Grade updatedGrade = editPersonDescriptor.getGrade().orElse(((Tutee) personToEdit).getGrade()); + EducationLevel updatedEducationalLevel = editPersonDescriptor.getEducationalLevel() + .orElse(((Tutee) personToEdit).getEducationLevel()); + School updatedSchool = editPersonDescriptor.getSchool().orElse(((Tutee) personToEdit).getSchool()); + + return new Tutee(updatedName, updatedPhone, updatedEmail, updatedAddress, updatedSubject, updatedGrade, + updatedEducationalLevel, updatedSchool, updatedTags); + } + //@@author return new Person(updatedName, updatedPhone, updatedEmail, updatedAddress, updatedTags); } @@ -140,6 +191,10 @@ public static class EditPersonDescriptor { private Email email; private Address address; private Set tags; + private Subject subject; + private Grade grade; + private EducationLevel educationLevel; + private School school; public EditPersonDescriptor() {} @@ -153,13 +208,18 @@ public EditPersonDescriptor(EditPersonDescriptor toCopy) { setEmail(toCopy.email); setAddress(toCopy.address); setTags(toCopy.tags); + setSubject(toCopy.subject); + setGrade(toCopy.grade); + setEducationLevel(toCopy.educationLevel); + setSchool(toCopy.school); } /** * Returns true if at least one field is edited. */ public boolean isAnyFieldEdited() { - return CollectionUtil.isAnyNonNull(this.name, this.phone, this.email, this.address, this.tags); + return CollectionUtil.isAnyNonNull(this.name, this.phone, this.email, this.address, this.tags, this.subject, + this.grade, this.educationLevel, this.school); } public void setName(Name name) { @@ -194,6 +254,40 @@ public Optional
getAddress() { return Optional.ofNullable(address); } + //@@author ChoChihTun + public void setSubject(Subject subject) { + this.subject = subject; + } + + public Optional getSubject() { + return Optional.ofNullable(subject); + } + + public void setGrade(Grade grade) { + this.grade = grade; + } + + public Optional getGrade() { + return Optional.ofNullable(grade); + } + + public void setEducationLevel(EducationLevel educationLevel) { + this.educationLevel = educationLevel; + } + + public Optional getEducationalLevel() { + return Optional.ofNullable(educationLevel); + } + + public void setSchool(School school) { + this.school = school; + } + + public Optional getSchool() { + return Optional.ofNullable(school); + } + //@@author + /** * Sets {@code tags} to this object's {@code tags}. * A defensive copy of {@code tags} is used internally. @@ -211,6 +305,31 @@ public Optional> getTags() { return (tags != null) ? Optional.of(Collections.unmodifiableSet(tags)) : Optional.empty(); } + //@@author ChoChihTun + /** + * Checks if tag is being edited + * + * @return true if tag is being edited + * false if tag is not being edited + */ + public boolean isTagEdited() { + return tags != null; + } + + /** + * Checks if any tutee field is being edited + * + * @return true if no field is being edited + * false if at least 1 field is being edited + */ + public boolean isAnyTuteeFieldEdited() { + return subject != null + || grade != null + || educationLevel != null + || school != null; + } + //@@author + @Override public boolean equals(Object other) { // short circuit if same object diff --git a/src/main/java/seedu/address/logic/commands/FindCommand.java b/src/main/java/seedu/address/logic/commands/FindCommand.java deleted file mode 100644 index b1e671f633d2..000000000000 --- a/src/main/java/seedu/address/logic/commands/FindCommand.java +++ /dev/null @@ -1,36 +0,0 @@ -package seedu.address.logic.commands; - -import seedu.address.model.person.NameContainsKeywordsPredicate; - -/** - * Finds and lists all persons in address book whose name contains any of the argument keywords. - * Keyword matching is case sensitive. - */ -public class FindCommand extends Command { - - public static final String COMMAND_WORD = "find"; - - public static final String MESSAGE_USAGE = COMMAND_WORD + ": Finds all persons whose names contain any of " - + "the specified keywords (case-sensitive) and displays them as a list with index numbers.\n" - + "Parameters: KEYWORD [MORE_KEYWORDS]...\n" - + "Example: " + COMMAND_WORD + " alice bob charlie"; - - private final NameContainsKeywordsPredicate predicate; - - public FindCommand(NameContainsKeywordsPredicate predicate) { - this.predicate = predicate; - } - - @Override - public CommandResult execute() { - model.updateFilteredPersonList(predicate); - return new CommandResult(getMessageForPersonListShownSummary(model.getFilteredPersonList().size())); - } - - @Override - public boolean equals(Object other) { - return other == this // short circuit if same object - || (other instanceof FindCommand // instanceof handles nulls - && this.predicate.equals(((FindCommand) other).predicate)); // state check - } -} diff --git a/src/main/java/seedu/address/logic/commands/FindPersonCommand.java b/src/main/java/seedu/address/logic/commands/FindPersonCommand.java new file mode 100644 index 000000000000..d4be84740976 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/FindPersonCommand.java @@ -0,0 +1,105 @@ +package seedu.address.logic.commands; + +import static seedu.address.model.person.PersonSortUtil.CATEGORY_EDUCATION_LEVEL; +import static seedu.address.model.person.PersonSortUtil.CATEGORY_GRADE; +import static seedu.address.model.person.PersonSortUtil.CATEGORY_NAME; +import static seedu.address.model.person.PersonSortUtil.CATEGORY_SCHOOL; +import static seedu.address.model.person.PersonSortUtil.CATEGORY_SUBJECT; + +import java.util.Arrays; +import java.util.function.Predicate; + +import seedu.address.model.Task; +import seedu.address.model.person.NameContainsKeywordsPredicate; +import seedu.address.model.person.Person; +import seedu.address.model.tutee.EducationLevelContainsKeywordsPredicate; +import seedu.address.model.tutee.GradeContainsKeywordsPredicate; +import seedu.address.model.tutee.SchoolContainsKeywordsPredicate; +import seedu.address.model.tutee.SubjectContainsKeywordsPredicate; + +//@@author yungyung04 +/** + * Finds and lists all persons in contact list based on the specified filter category. + */ +public class FindPersonCommand extends Command { + public static final String COMMAND_WORD = "findpersonby"; + public static final String COMMAND_ALIAS = "f"; + + public static final String MESSAGE_SUCCESS = "Find is successful."; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": lists all person that suit the specified category\n" + + "Parameters: filter_category keyword\n" + + "Choice of filter_categories: " + + CATEGORY_NAME + ", " + + CATEGORY_EDUCATION_LEVEL + ", " + + CATEGORY_GRADE + ", " + + CATEGORY_SCHOOL + ", " + + CATEGORY_SUBJECT + "\n" + + "Example: " + COMMAND_WORD + " " + CATEGORY_GRADE + " A"; + + private final String category; + private final String[] keywords; + private Predicate personPredicate; + private Predicate taskPredicate; + + public FindPersonCommand(String category, String[] keywords) { + this.category = category; + this.keywords = keywords; + } + + @Override + public CommandResult execute() { + switch (category) { + case CATEGORY_NAME: + personPredicate = new NameContainsKeywordsPredicate(Arrays.asList(keywords)); + model.updateFilteredPersonList(personPredicate); + break; + case CATEGORY_EDUCATION_LEVEL: + personPredicate = new EducationLevelContainsKeywordsPredicate(Arrays.asList(keywords)); + model.updateFilteredPersonList(personPredicate); + break; + case CATEGORY_GRADE: + personPredicate = new GradeContainsKeywordsPredicate(Arrays.asList(keywords)); + model.updateFilteredPersonList(personPredicate); + break; + case CATEGORY_SCHOOL: + personPredicate = new SchoolContainsKeywordsPredicate(Arrays.asList(keywords)); + model.updateFilteredPersonList(personPredicate); + break; + case CATEGORY_SUBJECT: + personPredicate = new SubjectContainsKeywordsPredicate(Arrays.asList(keywords)); + model.updateFilteredPersonList(personPredicate); + break; + default: + // invalid category should be detected in parser instead + assert (false); + } + return new CommandResult(MESSAGE_SUCCESS + "\n" + + getMessageForPersonListShownSummary(model.getFilteredPersonList().size())); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof FindPersonCommand // instanceof handles nulls + && category.equals(((FindPersonCommand) other).category) + && hasSameValue(keywords, ((FindPersonCommand) other).keywords)); + } + + /** + * Returns true if both the given arrays of String contain the same elements. + */ + private boolean hasSameValue(String[] firstKeywords, String[] secondKeywords) { + if (firstKeywords.length != secondKeywords.length) { + return false; + } + + for (int i = 0; i < firstKeywords.length; i++) { + if (!firstKeywords[i].equals(secondKeywords[i])) { + return false; + } + } + return true; + } +} diff --git a/src/main/java/seedu/address/logic/commands/FindTaskCommand.java b/src/main/java/seedu/address/logic/commands/FindTaskCommand.java new file mode 100644 index 000000000000..83c08807bdbc --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/FindTaskCommand.java @@ -0,0 +1,76 @@ +package seedu.address.logic.commands; + +import static seedu.address.model.task.TaskSortUtil.CATEGORY_MONTH; + +import java.util.Arrays; +import java.util.function.Predicate; + +import seedu.address.model.Task; +import seedu.address.model.task.MonthContainsKeywordsPredicate; + +//@@author yungyung04 +/** + * Finds and lists all tasks in the task list based on the specified filter category. + */ +public class FindTaskCommand extends Command { + public static final String COMMAND_WORD = "findtaskby"; + + public static final String MESSAGE_SUCCESS = "Find is successful."; + + public static final String INPUT_TYPE_BETWEEN = "between"; + public static final String INPUT_TYPE_NAMELY = "namely"; + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": lists all tasks that suit the specified category\n" + + "Parameters: CATEGORY FIND_TYPE KEYWORDS\n" + + "1st Example: " + COMMAND_WORD + " " + CATEGORY_MONTH + " " + INPUT_TYPE_BETWEEN + " April October\n" + + "2nd Example: " + COMMAND_WORD + " " + CATEGORY_MONTH + " " + INPUT_TYPE_NAMELY + + " 2 05 Aug December now"; + + private final String category; + private final String[] keywords; + private Predicate taskPredicate; + + public FindTaskCommand(String category, String[] keywords) { + this.category = category; + this.keywords = keywords; + } + + @Override + public CommandResult execute() { + switch (category) { + case CATEGORY_MONTH: + taskPredicate = new MonthContainsKeywordsPredicate(Arrays.asList(keywords)); + model.updateFilteredTaskList(taskPredicate); + break; + default: + // invalid category should be detected in parser instead + assert (false); + } + return new CommandResult(MESSAGE_SUCCESS + "\n" + + getMessageForTaskListShownSummary(model.getFilteredTaskList().size())); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof FindTaskCommand // instanceof handles nulls + && category.equals(((FindTaskCommand) other).category) + && hasSameValue(keywords, ((FindTaskCommand) other).keywords)); + } + + /** + * Returns true if both the given arrays of String contain the same elements. + */ + private boolean hasSameValue(String[] firstKeywords, String[] secondKeywords) { + if (firstKeywords.length != secondKeywords.length) { + return false; + } + + for (int i = 0; i < firstKeywords.length; i++) { + if (!firstKeywords[i].equals(secondKeywords[i])) { + return false; + } + } + return true; + } +} diff --git a/src/main/java/seedu/address/logic/commands/HistoryCommand.java b/src/main/java/seedu/address/logic/commands/HistoryCommand.java index f87abee5511d..6abf45f550f5 100644 --- a/src/main/java/seedu/address/logic/commands/HistoryCommand.java +++ b/src/main/java/seedu/address/logic/commands/HistoryCommand.java @@ -15,6 +15,8 @@ public class HistoryCommand extends Command { public static final String COMMAND_WORD = "history"; + public static final String COMMAND_ALIAS = "h"; + public static final String MESSAGE_SUCCESS = "Entered commands (from most recent to earliest):\n%1$s"; public static final String MESSAGE_NO_HISTORY = "You have not yet entered any commands."; diff --git a/src/main/java/seedu/address/logic/commands/ListCommand.java b/src/main/java/seedu/address/logic/commands/ListCommand.java index 7b6463780824..2631e4dda649 100644 --- a/src/main/java/seedu/address/logic/commands/ListCommand.java +++ b/src/main/java/seedu/address/logic/commands/ListCommand.java @@ -8,6 +8,7 @@ public class ListCommand extends Command { public static final String COMMAND_WORD = "list"; + public static final String COMMAND_ALIAS = "l"; public static final String MESSAGE_SUCCESS = "Listed all persons"; diff --git a/src/main/java/seedu/address/logic/commands/ListTaskCommand.java b/src/main/java/seedu/address/logic/commands/ListTaskCommand.java new file mode 100644 index 000000000000..5b1066ae3f1f --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/ListTaskCommand.java @@ -0,0 +1,24 @@ +package seedu.address.logic.commands; + +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_TASKS; + +/** + * Lists all tasks in the application to the user. + */ +//@@author a-shakra + +public class ListTaskCommand extends Command { + + public static final String COMMAND_WORD = "listtask"; + public static final String COMMAND_ALIAS = "lt"; + + public static final String MESSAGE_SUCCESS = "Listed all tasks"; + + + @Override + public CommandResult execute() { + model.updateFilteredTaskList(PREDICATE_SHOW_ALL_TASKS); + return new CommandResult(MESSAGE_SUCCESS); + } + +} diff --git a/src/main/java/seedu/address/logic/commands/ListTuteeCommand.java b/src/main/java/seedu/address/logic/commands/ListTuteeCommand.java new file mode 100644 index 000000000000..93401be80752 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/ListTuteeCommand.java @@ -0,0 +1,20 @@ +package seedu.address.logic.commands; + +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_TUTEES; + +//@@author yungyung04 +/** + * Lists all tutees in the application to the user. + */ +public class ListTuteeCommand extends Command { + + public static final String COMMAND_WORD = "listtutee"; + + public static final String MESSAGE_SUCCESS = "Listed all tutees"; + + @Override + public CommandResult execute() { + model.updateFilteredPersonList(PREDICATE_SHOW_ALL_TUTEES); + return new CommandResult(MESSAGE_SUCCESS); + } +} diff --git a/src/main/java/seedu/address/logic/commands/RedoCommand.java b/src/main/java/seedu/address/logic/commands/RedoCommand.java index 7b99d0f372fc..311c95c1a1f2 100644 --- a/src/main/java/seedu/address/logic/commands/RedoCommand.java +++ b/src/main/java/seedu/address/logic/commands/RedoCommand.java @@ -13,6 +13,7 @@ public class RedoCommand extends Command { public static final String COMMAND_WORD = "redo"; + public static final String COMMAND_ALIAS = "r"; public static final String MESSAGE_SUCCESS = "Redo success!"; public static final String MESSAGE_FAILURE = "No more commands to redo!"; diff --git a/src/main/java/seedu/address/logic/commands/SelectCommand.java b/src/main/java/seedu/address/logic/commands/SelectCommand.java index 9e3840a9dde6..4ff8d1936393 100644 --- a/src/main/java/seedu/address/logic/commands/SelectCommand.java +++ b/src/main/java/seedu/address/logic/commands/SelectCommand.java @@ -9,12 +9,14 @@ import seedu.address.logic.commands.exceptions.CommandException; import seedu.address.model.person.Person; + /** * Selects a person identified using it's last displayed index from the address book. */ public class SelectCommand extends Command { public static final String COMMAND_WORD = "select"; + public static final String COMMAND_ALIAS = "s"; public static final String MESSAGE_USAGE = COMMAND_WORD + ": Selects the person identified by the index number used in the last person listing.\n" @@ -37,8 +39,8 @@ public CommandResult execute() throws CommandException { if (targetIndex.getZeroBased() >= lastShownList.size()) { throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); } - - EventsCenter.getInstance().post(new JumpToListRequestEvent(targetIndex)); + JumpToListRequestEvent jumpToListRequestEvent = new JumpToListRequestEvent(targetIndex); + EventsCenter.getInstance().post(jumpToListRequestEvent); return new CommandResult(String.format(MESSAGE_SELECT_PERSON_SUCCESS, targetIndex.getOneBased())); } diff --git a/src/main/java/seedu/address/logic/commands/SortPersonCommand.java b/src/main/java/seedu/address/logic/commands/SortPersonCommand.java new file mode 100644 index 000000000000..e4a04ed7000d --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/SortPersonCommand.java @@ -0,0 +1,59 @@ +package seedu.address.logic.commands; + +import static seedu.address.model.person.PersonSortUtil.CATEGORY_EDUCATION_LEVEL; +import static seedu.address.model.person.PersonSortUtil.CATEGORY_GRADE; +import static seedu.address.model.person.PersonSortUtil.CATEGORY_NAME; +import static seedu.address.model.person.PersonSortUtil.CATEGORY_SCHOOL; +import static seedu.address.model.person.PersonSortUtil.CATEGORY_SUBJECT; + +import java.util.Comparator; + +import seedu.address.model.person.Person; +import seedu.address.model.person.PersonSortUtil; + +//@@author yungyung04 +/** + * Sorts all persons from the last shown list lexicographically according to the specified sorting category. + * Since tutee contains specific information such as grade, + * a Person who is not a tutee will be listed last when such information is selected to be the sorting category. + */ +public class SortPersonCommand extends Command { + public static final String COMMAND_WORD = "sortpersonby"; + public static final String COMMAND_ALIAS = "spb"; + + public static final String MESSAGE_SUCCESS = "sorted list of persons successfully"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": sorts all visible persons lexicographically according to the specified sorting category.\n" + + "Persons who are not Tutees will be listed last when a tutee detail is the selected category " + + "(refer to User Guide)\n" + + "Parameter: sort_category\n" + + "Choice of sort_categories: " + + CATEGORY_NAME + ", " + + CATEGORY_EDUCATION_LEVEL + ", " + + CATEGORY_GRADE + ", " + + CATEGORY_SCHOOL + ", " + + CATEGORY_SUBJECT + "\n" + + "Example: " + COMMAND_WORD + " " + CATEGORY_GRADE; + + private final String category; + private final Comparator comparator; + + public SortPersonCommand(String category) { + this.category = category; + comparator = PersonSortUtil.getComparator(category); + } + + @Override + public CommandResult execute() { + model.sortFilteredPersonList(comparator); + return new CommandResult(MESSAGE_SUCCESS); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof SortPersonCommand // instanceof handles nulls + && category.equals(((SortPersonCommand) other).category)); + } +} diff --git a/src/main/java/seedu/address/logic/commands/SortTaskCommand.java b/src/main/java/seedu/address/logic/commands/SortTaskCommand.java new file mode 100644 index 000000000000..b5e5ee2999c2 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/SortTaskCommand.java @@ -0,0 +1,51 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.model.task.TaskSortUtil.CATEGORY_DATE_TIME; +import static seedu.address.model.task.TaskSortUtil.CATEGORY_MONTH; + +import java.util.Comparator; + +import seedu.address.model.Task; +import seedu.address.model.task.TaskSortUtil; + +//@@author yungyung04 +/** + * Sorts all tasks from the last shown list according to the specified sorting category in an increasing order + */ +public class SortTaskCommand extends Command { + public static final String COMMAND_WORD = "sorttaskby"; + public static final String COMMAND_ALIAS = "stb"; + public static final String MESSAGE_SUCCESS = "sorted list of tasks successfully"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + "Sorts all visible tasks according to the specified sorting category in an increasing order\n" + + "Parameter: sort_category\n" + + "Choice of sort_categories: " + + CATEGORY_MONTH + ", " + + CATEGORY_DATE_TIME + "\n" + + "Example: " + COMMAND_WORD + " " + CATEGORY_MONTH; + + private final String category; + private final Comparator comparator; + + public SortTaskCommand(String category) { + requireNonNull(category); + this.category = category; + comparator = TaskSortUtil.getComparator(category); + } + + @Override + public CommandResult execute() { + requireNonNull(comparator); + model.sortFilteredTaskList(comparator); + return new CommandResult(MESSAGE_SUCCESS); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof SortTaskCommand // instanceof handles nulls + && category.equals(((SortTaskCommand) other).category)); + } +} diff --git a/src/main/java/seedu/address/logic/commands/UndoCommand.java b/src/main/java/seedu/address/logic/commands/UndoCommand.java index 1f3dcea8bbaa..7d62dcc53d38 100644 --- a/src/main/java/seedu/address/logic/commands/UndoCommand.java +++ b/src/main/java/seedu/address/logic/commands/UndoCommand.java @@ -13,6 +13,7 @@ public class UndoCommand extends Command { public static final String COMMAND_WORD = "undo"; + public static final String COMMAND_ALIAS = "u"; public static final String MESSAGE_SUCCESS = "Undo success!"; public static final String MESSAGE_FAILURE = "No more commands to undo!"; diff --git a/src/main/java/seedu/address/logic/commands/UndoableCommand.java b/src/main/java/seedu/address/logic/commands/UndoableCommand.java index c107ffcd9cb3..ca9bc1145060 100644 --- a/src/main/java/seedu/address/logic/commands/UndoableCommand.java +++ b/src/main/java/seedu/address/logic/commands/UndoableCommand.java @@ -3,6 +3,7 @@ import static java.util.Objects.requireNonNull; import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_TASKS; import seedu.address.logic.commands.exceptions.CommandException; import seedu.address.model.AddressBook; @@ -39,6 +40,7 @@ protected final void undo() { requireAllNonNull(model, previousAddressBook); model.resetData(previousAddressBook); model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); + model.updateFilteredTaskList(PREDICATE_SHOW_ALL_TASKS); } /** @@ -54,6 +56,7 @@ protected final void redo() { + "it should not fail now"); } model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); + model.updateFilteredTaskList(PREDICATE_SHOW_ALL_TASKS); } @Override diff --git a/src/main/java/seedu/address/logic/parser/AddCommandParser.java b/src/main/java/seedu/address/logic/parser/AddCommandParser.java index 3c729b388554..0f3ac0281ebb 100644 --- a/src/main/java/seedu/address/logic/parser/AddCommandParser.java +++ b/src/main/java/seedu/address/logic/parser/AddCommandParser.java @@ -44,7 +44,7 @@ public AddCommand parse(String args) throws ParseException { Phone phone = ParserUtil.parsePhone(argMultimap.getValue(PREFIX_PHONE)).get(); Email email = ParserUtil.parseEmail(argMultimap.getValue(PREFIX_EMAIL)).get(); Address address = ParserUtil.parseAddress(argMultimap.getValue(PREFIX_ADDRESS)).get(); - Set tagList = ParserUtil.parseTags(argMultimap.getAllValues(PREFIX_TAG)); + Set tagList = ParserUtil.parsePersonTags(argMultimap.getAllValues(PREFIX_TAG)); Person person = new Person(name, phone, email, address, tagList); diff --git a/src/main/java/seedu/address/logic/parser/AddPersonalTaskCommandParser.java b/src/main/java/seedu/address/logic/parser/AddPersonalTaskCommandParser.java new file mode 100644 index 000000000000..cea8b8564c33 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/AddPersonalTaskCommandParser.java @@ -0,0 +1,55 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_DATE_TIME; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_DURATION; + +import java.time.LocalDateTime; +import java.time.format.DateTimeParseException; + +import seedu.address.logic.commands.AddPersonalTaskCommand; +import seedu.address.logic.parser.exceptions.DurationParseException; +import seedu.address.logic.parser.exceptions.ParseException; + +import seedu.address.model.personal.PersonalTask; + +//@@author yungyung04 + +/** + * Parses input arguments and creates a new AddPersonalTaskCommand object. + */ +public class AddPersonalTaskCommandParser implements Parser { + + private static final String INPUT_FORMAT_VALIDATION_REGEX = "(\\d{2}/\\d{2}/\\d{4})\\s\\d{2}:\\d{2}\\s" + + "\\d{1,2}h\\d{1,2}m.*"; + private static final int MAXIMUM_AMOUNT_OF_TASK_PARAMETER = 4; + private static final int INDEX_OF_DATE = 0; + private static final int INDEX_OF_TIME = 1; + private static final int INDEX_OF_DURATION = 2; + + /** + * Parses the given {@code String} of arguments in the context of the AddPersonalTaskCommand + * and returns an AddPersonalTaskCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public AddPersonalTaskCommand parse(String task) throws ParseException { + if (!task.trim().matches(INPUT_FORMAT_VALIDATION_REGEX)) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddPersonalTaskCommand.MESSAGE_USAGE)); + } + + String[] arguments = task.trim().split("\\s+", MAXIMUM_AMOUNT_OF_TASK_PARAMETER); + try { + LocalDateTime taskDateTime = + ParserUtil.parseDateTime(arguments[INDEX_OF_DATE] + " " + arguments[INDEX_OF_TIME]); + String duration = ParserUtil.parseDuration(arguments[INDEX_OF_DURATION]); + String description = ParserUtil.parseDescription(arguments, MAXIMUM_AMOUNT_OF_TASK_PARAMETER); + + return new AddPersonalTaskCommand(new PersonalTask(taskDateTime, duration, description)); + } catch (DateTimeParseException dtpe) { + throw new ParseException(MESSAGE_INVALID_DATE_TIME); + } catch (DurationParseException dpe) { + throw new ParseException(MESSAGE_INVALID_DURATION); + } + } +} diff --git a/src/main/java/seedu/address/logic/parser/AddTuitionTaskCommandParser.java b/src/main/java/seedu/address/logic/parser/AddTuitionTaskCommandParser.java new file mode 100644 index 000000000000..815cfd32870c --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/AddTuitionTaskCommandParser.java @@ -0,0 +1,61 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_DATE_TIME; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_DURATION; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX; + +import java.time.LocalDateTime; +import java.time.format.DateTimeParseException; + +import seedu.address.commons.core.index.Index; +import seedu.address.commons.exceptions.IllegalValueException; + +import seedu.address.logic.commands.AddTuitionTaskCommand; +import seedu.address.logic.parser.exceptions.DurationParseException; +import seedu.address.logic.parser.exceptions.ParseException; + + +//@@author yungyung04 +/** + * Parses input arguments and creates a new AddTuitionTaskCommand object + */ +public class AddTuitionTaskCommandParser implements Parser { + + private static final String INPUT_FORMAT_VALIDATION_REGEX = "\\d+\\s(\\d{2}/\\d{2}/\\d{4})\\s\\d{2}:\\d{2}\\s" + + "\\d{1,2}h\\d{1,2}m.*"; + private static final int MAXIMUM_AMOUNT_OF_TASK_PARAMETER = 5; + private static final int INDEX_OF_PERSON_INDEX = 0; + private static final int INDEX_OF_DATE = 1; + private static final int INDEX_OF_TIME = 2; + private static final int INDEX_OF_DURATION = 3; + + /** + * Parses the given {@code String} of arguments in the context of the AddTuitionTaskCommand + * and returns an AddTuitionTaskCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public AddTuitionTaskCommand parse(String args) throws ParseException { + if (!args.trim().matches(INPUT_FORMAT_VALIDATION_REGEX)) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddTuitionTaskCommand.MESSAGE_USAGE)); + } + + String[] arguments = args.trim().split("\\s+", MAXIMUM_AMOUNT_OF_TASK_PARAMETER); + try { + Index personIndex = ParserUtil.parseIndex(arguments[INDEX_OF_PERSON_INDEX]); + LocalDateTime taskDateTime = + ParserUtil.parseDateTime(arguments[INDEX_OF_DATE] + " " + arguments[INDEX_OF_TIME]); + String duration = ParserUtil.parseDuration(arguments[INDEX_OF_DURATION]); + String description = ParserUtil.parseDescription(arguments, MAXIMUM_AMOUNT_OF_TASK_PARAMETER); + + return new AddTuitionTaskCommand(personIndex, taskDateTime, duration, description); + } catch (DateTimeParseException dtpe) { + throw new ParseException(MESSAGE_INVALID_DATE_TIME); + } catch (DurationParseException dpe) { + throw new ParseException(MESSAGE_INVALID_DURATION); + } catch (IllegalValueException ive) { + throw new ParseException(MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + } + } +} diff --git a/src/main/java/seedu/address/logic/parser/AddTuteeCommandParser.java b/src/main/java/seedu/address/logic/parser/AddTuteeCommandParser.java new file mode 100644 index 000000000000..7561ab68174a --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/AddTuteeCommandParser.java @@ -0,0 +1,80 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_EDUCATION_LEVEL; +import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; +import static seedu.address.logic.parser.CliSyntax.PREFIX_GRADE; +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_SCHOOL; +import static seedu.address.logic.parser.CliSyntax.PREFIX_SUBJECT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; + +import java.util.Set; +import java.util.stream.Stream; + +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.logic.commands.AddTuteeCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.person.Address; +import seedu.address.model.person.Email; +import seedu.address.model.person.Name; +import seedu.address.model.person.Phone; +import seedu.address.model.tag.Tag; +import seedu.address.model.tutee.EducationLevel; +import seedu.address.model.tutee.Grade; +import seedu.address.model.tutee.School; +import seedu.address.model.tutee.Subject; +import seedu.address.model.tutee.Tutee; + +//@@author ChoChihTun +/** + * Parses input arguments and creates a new AddTuteeCommand object + */ +public class AddTuteeCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the AddTuteeCommand + * and returns an AddTuteeCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public AddTuteeCommand parse(String args) throws ParseException { + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS, + PREFIX_SUBJECT, PREFIX_GRADE, PREFIX_EDUCATION_LEVEL, PREFIX_SCHOOL, PREFIX_TAG); + + if (!arePrefixesPresent(argMultimap, PREFIX_NAME, PREFIX_ADDRESS, PREFIX_PHONE, PREFIX_EMAIL, + PREFIX_SUBJECT, PREFIX_GRADE, PREFIX_EDUCATION_LEVEL, PREFIX_SCHOOL) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddTuteeCommand.MESSAGE_USAGE)); + } + + try { + Name name = ParserUtil.parseName(argMultimap.getValue(PREFIX_NAME)).get(); + Phone phone = ParserUtil.parsePhone(argMultimap.getValue(PREFIX_PHONE)).get(); + Email email = ParserUtil.parseEmail(argMultimap.getValue(PREFIX_EMAIL)).get(); + Address address = ParserUtil.parseAddress(argMultimap.getValue(PREFIX_ADDRESS)).get(); + Subject subject = ParserUtil.parseSubject(argMultimap.getValue(PREFIX_SUBJECT)).get(); + Grade grade = ParserUtil.parseGrade(argMultimap.getValue(PREFIX_GRADE)).get(); + EducationLevel educationLevel = ParserUtil.parseEducationLevel( + argMultimap.getValue(PREFIX_EDUCATION_LEVEL)).get(); + School school = ParserUtil.parseSchool(argMultimap.getValue(PREFIX_SCHOOL)).get(); + Set tagList = ParserUtil.parseTuteeTags(argMultimap.getAllValues(PREFIX_TAG)); + + Tutee person = new Tutee(name, phone, email, address, subject, grade, educationLevel, school, tagList); + + return new AddTuteeCommand(person); + } catch (IllegalValueException ive) { + throw new ParseException(ive.getMessage(), ive); + } + } + + /** + * 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/AddressBookParser.java b/src/main/java/seedu/address/logic/parser/AddressBookParser.java index b7d57f5db86a..6e8111457c68 100644 --- a/src/main/java/seedu/address/logic/parser/AddressBookParser.java +++ b/src/main/java/seedu/address/logic/parser/AddressBookParser.java @@ -7,17 +7,26 @@ import java.util.regex.Pattern; import seedu.address.logic.commands.AddCommand; +import seedu.address.logic.commands.AddPersonalTaskCommand; +import seedu.address.logic.commands.AddTuitionTaskCommand; +import seedu.address.logic.commands.AddTuteeCommand; +import seedu.address.logic.commands.ChangeCommand; import seedu.address.logic.commands.ClearCommand; import seedu.address.logic.commands.Command; import seedu.address.logic.commands.DeleteCommand; +import seedu.address.logic.commands.DeleteTaskCommand; import seedu.address.logic.commands.EditCommand; import seedu.address.logic.commands.ExitCommand; -import seedu.address.logic.commands.FindCommand; +import seedu.address.logic.commands.FindPersonCommand; +import seedu.address.logic.commands.FindTaskCommand; import seedu.address.logic.commands.HelpCommand; import seedu.address.logic.commands.HistoryCommand; import seedu.address.logic.commands.ListCommand; +import seedu.address.logic.commands.ListTaskCommand; +import seedu.address.logic.commands.ListTuteeCommand; import seedu.address.logic.commands.RedoCommand; -import seedu.address.logic.commands.SelectCommand; +import seedu.address.logic.commands.SortPersonCommand; +import seedu.address.logic.commands.SortTaskCommand; import seedu.address.logic.commands.UndoCommand; import seedu.address.logic.parser.exceptions.ParseException; @@ -49,27 +58,31 @@ public Command parseCommand(String userInput) throws ParseException { switch (commandWord) { case AddCommand.COMMAND_WORD: + case AddCommand.COMMAND_ALIAS: return new AddCommandParser().parse(arguments); case EditCommand.COMMAND_WORD: + case EditCommand.COMMAND_ALIAS: return new EditCommandParser().parse(arguments); - case SelectCommand.COMMAND_WORD: - return new SelectCommandParser().parse(arguments); - case DeleteCommand.COMMAND_WORD: + case DeleteCommand.COMMAND_ALIAS: return new DeleteCommandParser().parse(arguments); case ClearCommand.COMMAND_WORD: + case ClearCommand.COMMAND_ALIAS: return new ClearCommand(); - case FindCommand.COMMAND_WORD: - return new FindCommandParser().parse(arguments); - case ListCommand.COMMAND_WORD: + case ListCommand.COMMAND_ALIAS: return new ListCommand(); + case ListTaskCommand.COMMAND_WORD: + case ListTaskCommand.COMMAND_ALIAS: + return new ListTaskCommand(); + case HistoryCommand.COMMAND_WORD: + case HistoryCommand.COMMAND_ALIAS: return new HistoryCommand(); case ExitCommand.COMMAND_WORD: @@ -79,11 +92,46 @@ public Command parseCommand(String userInput) throws ParseException { return new HelpCommand(); case UndoCommand.COMMAND_WORD: + case UndoCommand.COMMAND_ALIAS: return new UndoCommand(); case RedoCommand.COMMAND_WORD: + case RedoCommand.COMMAND_ALIAS: return new RedoCommand(); + case AddTuitionTaskCommand.COMMAND_WORD: + return new AddTuitionTaskCommandParser().parse(arguments); + + case AddPersonalTaskCommand.COMMAND_WORD: + return new AddPersonalTaskCommandParser().parse(arguments); + + case DeleteTaskCommand.COMMAND_WORD: + return new DeleteTaskCommandParser().parse(arguments); + + case AddTuteeCommand.COMMAND_WORD: + return new AddTuteeCommandParser().parse(arguments); + + case ListTuteeCommand.COMMAND_WORD: + return new ListTuteeCommand(); + + case FindPersonCommand.COMMAND_WORD: + case FindPersonCommand.COMMAND_ALIAS: + return new FindPersonCommandParser().parse(arguments); + + case FindTaskCommand.COMMAND_WORD: + return new FindTaskCommandParser().parse(arguments); + + case SortPersonCommand.COMMAND_WORD: + case SortPersonCommand.COMMAND_ALIAS: + return new SortPersonCommandParser().parse(arguments); + + case SortTaskCommand.COMMAND_WORD: + case SortTaskCommand.COMMAND_ALIAS: + return new SortTaskCommandParser().parse(arguments); + + case ChangeCommand.COMMAND_WORD: + return new ChangeCommandParser().parse(arguments); + default: throw new ParseException(MESSAGE_UNKNOWN_COMMAND); } diff --git a/src/main/java/seedu/address/logic/parser/ChangeCommandParser.java b/src/main/java/seedu/address/logic/parser/ChangeCommandParser.java new file mode 100644 index 000000000000..b330410184c6 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/ChangeCommandParser.java @@ -0,0 +1,63 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.logic.commands.ChangeCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.logic.parser.exceptions.SameTimeUnitException; + +//@@author ChoChihTun +/** + * Parses input arguments and creates a new ChangeCommand object + */ +public class ChangeCommandParser implements Parser { + private static final String DAY = "d"; + private static final String WEEK = "w"; + private static final String MONTH = "m"; + private static final String YEAR = "y"; + + /** + * Parses the given {@code String} of arguments in the context of the ChangeCommand + * and returns an ChangeCommand object for execution. + * + * @throws ParseException if the user input does not conform the expected format + */ + public ChangeCommand parse(String args) throws ParseException { + try { + String timeUnit = ParserUtil.parseTimeUnit(args); + return new ChangeCommand(timeUnit); + } catch (IllegalValueException ive) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, ChangeCommand.MESSAGE_USAGE)); + } catch (SameTimeUnitException stue) { + throw new ParseException(stue.getMessage()); + } + } + + /** + * Checks if the user input view page time unit is valid + * + * @param trimmedTimeUnit to be checked + * @return true if view page time unit is valid + * false if the view page time unit is invalid + */ + public static boolean isValidTimeUnit(String trimmedTimeUnit) { + return (trimmedTimeUnit.equals(DAY) + || trimmedTimeUnit.equals(WEEK) + || trimmedTimeUnit.equals(MONTH) + || trimmedTimeUnit.equals(YEAR)); + } + + /** + * Checks if the new view page time unit clashes with the current time unit + * + * @param timeUnit to be checked + * @return true if the view page time unit clashes with the current time unit + * false if there is no clash + */ + public static boolean isTimeUnitClash(String timeUnit) { + String currentViewPage = ChangeCommand.getTimeUnit(); + return (timeUnit.equals(currentViewPage)); + } +} diff --git a/src/main/java/seedu/address/logic/parser/CliSyntax.java b/src/main/java/seedu/address/logic/parser/CliSyntax.java index 75b1a9bf1190..bab3e2ba7eb5 100644 --- a/src/main/java/seedu/address/logic/parser/CliSyntax.java +++ b/src/main/java/seedu/address/logic/parser/CliSyntax.java @@ -11,5 +11,8 @@ public class CliSyntax { public static final Prefix PREFIX_EMAIL = new Prefix("e/"); public static final Prefix PREFIX_ADDRESS = new Prefix("a/"); public static final Prefix PREFIX_TAG = new Prefix("t/"); - + public static final Prefix PREFIX_SUBJECT = new Prefix("s/"); + public static final Prefix PREFIX_GRADE = new Prefix("g/"); + public static final Prefix PREFIX_EDUCATION_LEVEL = new Prefix("edu/"); + public static final Prefix PREFIX_SCHOOL = new Prefix("sch/"); } 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 000000000000..cd0e54ff4dc9 --- /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.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import seedu.address.commons.core.index.Index; +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.logic.commands.DeleteTaskCommand; +import seedu.address.logic.parser.exceptions.ParseException; + +//@@author yungyung04 +/** + * 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 an 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 (IllegalValueException ive) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteTaskCommand.MESSAGE_USAGE)); + } + } +} diff --git a/src/main/java/seedu/address/logic/parser/EditCommandParser.java b/src/main/java/seedu/address/logic/parser/EditCommandParser.java index c9cdbed26cf1..1b52cc95ffe3 100644 --- a/src/main/java/seedu/address/logic/parser/EditCommandParser.java +++ b/src/main/java/seedu/address/logic/parser/EditCommandParser.java @@ -3,9 +3,13 @@ import static java.util.Objects.requireNonNull; import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_EDUCATION_LEVEL; import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; +import static seedu.address.logic.parser.CliSyntax.PREFIX_GRADE; 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_SCHOOL; +import static seedu.address.logic.parser.CliSyntax.PREFIX_SUBJECT; import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; import java.util.Collection; @@ -33,7 +37,8 @@ public class EditCommandParser implements Parser { public EditCommand parse(String args) throws ParseException { requireNonNull(args); ArgumentMultimap argMultimap = - ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS, PREFIX_TAG); + ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS, PREFIX_TAG, + PREFIX_SUBJECT, PREFIX_GRADE, PREFIX_EDUCATION_LEVEL, PREFIX_SCHOOL); Index index; @@ -49,6 +54,11 @@ public EditCommand parse(String args) throws ParseException { ParserUtil.parsePhone(argMultimap.getValue(PREFIX_PHONE)).ifPresent(editPersonDescriptor::setPhone); ParserUtil.parseEmail(argMultimap.getValue(PREFIX_EMAIL)).ifPresent(editPersonDescriptor::setEmail); ParserUtil.parseAddress(argMultimap.getValue(PREFIX_ADDRESS)).ifPresent(editPersonDescriptor::setAddress); + ParserUtil.parseSubject(argMultimap.getValue(PREFIX_SUBJECT)).ifPresent(editPersonDescriptor::setSubject); + ParserUtil.parseGrade(argMultimap.getValue(PREFIX_GRADE)).ifPresent(editPersonDescriptor::setGrade); + ParserUtil.parseEducationLevel(argMultimap.getValue(PREFIX_EDUCATION_LEVEL)) + .ifPresent(editPersonDescriptor::setEducationLevel); + ParserUtil.parseSchool(argMultimap.getValue(PREFIX_SCHOOL)).ifPresent(editPersonDescriptor::setSchool); parseTagsForEdit(argMultimap.getAllValues(PREFIX_TAG)).ifPresent(editPersonDescriptor::setTags); } catch (IllegalValueException ive) { throw new ParseException(ive.getMessage(), ive); diff --git a/src/main/java/seedu/address/logic/parser/FindCommandParser.java b/src/main/java/seedu/address/logic/parser/FindCommandParser.java deleted file mode 100644 index b186a967cb94..000000000000 --- a/src/main/java/seedu/address/logic/parser/FindCommandParser.java +++ /dev/null @@ -1,33 +0,0 @@ -package seedu.address.logic.parser; - -import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; - -import java.util.Arrays; - -import seedu.address.logic.commands.FindCommand; -import seedu.address.logic.parser.exceptions.ParseException; -import seedu.address.model.person.NameContainsKeywordsPredicate; - -/** - * Parses input arguments and creates a new FindCommand object - */ -public class FindCommandParser implements Parser { - - /** - * Parses the given {@code String} of arguments in the context of the FindCommand - * and returns an FindCommand object for execution. - * @throws ParseException if the user input does not conform the expected format - */ - public FindCommand parse(String args) throws ParseException { - String trimmedArgs = args.trim(); - if (trimmedArgs.isEmpty()) { - throw new ParseException( - String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindCommand.MESSAGE_USAGE)); - } - - String[] nameKeywords = trimmedArgs.split("\\s+"); - - return new FindCommand(new NameContainsKeywordsPredicate(Arrays.asList(nameKeywords))); - } - -} diff --git a/src/main/java/seedu/address/logic/parser/FindPersonCommandParser.java b/src/main/java/seedu/address/logic/parser/FindPersonCommandParser.java new file mode 100644 index 000000000000..df273a7d0958 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/FindPersonCommandParser.java @@ -0,0 +1,53 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_FILTER_CATEGORY; +import static seedu.address.model.person.PersonSortUtil.CATEGORY_EDUCATION_LEVEL; +import static seedu.address.model.person.PersonSortUtil.CATEGORY_GRADE; +import static seedu.address.model.person.PersonSortUtil.CATEGORY_NAME; +import static seedu.address.model.person.PersonSortUtil.CATEGORY_SCHOOL; +import static seedu.address.model.person.PersonSortUtil.CATEGORY_SUBJECT; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import seedu.address.logic.commands.FindPersonCommand; +import seedu.address.logic.parser.exceptions.ParseException; + +//@@author yungyung04 +/** + * Parses input arguments and creates a new FindPersonCommand object + */ +public class FindPersonCommandParser implements Parser { + + private static final int EXPECTED_AMOUNT_OF_PARAMETERS = 2; + private static final int INDEX_OF_FILTER_CATEGORY = 0; + private static final int INDEX_OF_KEYWORDS = 1; + + private List validCategories = + new ArrayList<>(Arrays.asList(CATEGORY_NAME, CATEGORY_EDUCATION_LEVEL, CATEGORY_GRADE, + CATEGORY_SCHOOL, CATEGORY_SUBJECT)); + + /** + * Parses the given {@code String} of arguments in the context of the FindPersonCommand + * and returns a FindPersonCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public FindPersonCommand parse(String args) throws ParseException { + String[] arguments = args.trim().toLowerCase().split("\\s+", EXPECTED_AMOUNT_OF_PARAMETERS); + + if (arguments.length < EXPECTED_AMOUNT_OF_PARAMETERS) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindPersonCommand.MESSAGE_USAGE)); + } + + String filterCategory = arguments[INDEX_OF_FILTER_CATEGORY]; + String[] keywords = arguments[INDEX_OF_KEYWORDS].split("\\s+"); + + if (!validCategories.contains(filterCategory)) { + throw new ParseException(String.format(MESSAGE_INVALID_FILTER_CATEGORY, FindPersonCommand.MESSAGE_USAGE)); + } + + return new FindPersonCommand(filterCategory, keywords); + } +} diff --git a/src/main/java/seedu/address/logic/parser/FindTaskCommandParser.java b/src/main/java/seedu/address/logic/parser/FindTaskCommandParser.java new file mode 100644 index 000000000000..414f69398f59 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/FindTaskCommandParser.java @@ -0,0 +1,229 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_DATE_TIME; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_FILTER_CATEGORY; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_INPUT_TYPES; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_KEYWORD_GIVEN; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_MONTH_RANGE_FORMAT; +import static seedu.address.model.task.TaskSortUtil.CATEGORY_MONTH; + +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeFormatterBuilder; +import java.time.format.DateTimeParseException; +import java.time.temporal.ChronoField; +import java.time.temporal.TemporalAccessor; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Locale; + +import seedu.address.logic.commands.FindTaskCommand; +import seedu.address.logic.parser.exceptions.InvalidBoundariesException; +import seedu.address.logic.parser.exceptions.ParseException; + +//@@author yungyung04 +/** + * Parses input arguments and creates a new FindTaskCommand object + */ +public class FindTaskCommandParser implements Parser { + + private static final int EXPECTED_AMOUNT_OF_PARAMETERS = 3; + private static final int INDEX_OF_FILTER_CATEGORY = 0; + private static final int INDEX_OF_INPUT_TYPE = 1; + private static final int INDEX_OF_KEYWORDS = 2; + private static final int INDEX_OF_FIRST_KEYWORD = 0; + private static final int INDEX_OF_SECOND_KEYWORD = 1; + private static final int INVALID_MONTH = 0; + private static final int MONTH_WITH_MMM_FORMAT_CHARACTER_LENGTH = 3; + private static final int REQUIRED_AMOUNT_OF_BOUNDARIES = 2; + private static final int MONTH_WITH_MM_FORMAT_CHARACTER_LENGTH = 2; + private static final int AMOUNT_OF_MONTHS = 12; + private static final String INPUT_TYPE_NAMELY = "namely"; + private static final String INPUT_TYPE_BETWEEN = "between"; + private static final DateTimeFormatter FORMATTER_MONTH_MM = new DateTimeFormatterBuilder().parseCaseInsensitive() + .appendPattern("MM").toFormatter(Locale.ENGLISH); + private static final DateTimeFormatter FORMATTER_MONTH_MMM = new DateTimeFormatterBuilder().parseCaseInsensitive() + .appendPattern("MMM").toFormatter(Locale.ENGLISH); + private static final DateTimeFormatter FORMATTER_MONTH_MMMM = new DateTimeFormatterBuilder().parseCaseInsensitive() + .appendPattern("MMMM").toFormatter(Locale.ENGLISH); + + private List validCategories = new ArrayList<>(Arrays.asList(CATEGORY_MONTH)); + private List validInputTypes = new ArrayList<>(Arrays.asList(INPUT_TYPE_NAMELY, INPUT_TYPE_BETWEEN)); + + /** + * Parses the given {@code String} of arguments in the context of the FindTaskCommand + * and returns a FindTaskCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public FindTaskCommand parse(String args) throws ParseException { + String[] arguments = args.trim().toLowerCase().split("\\s+", EXPECTED_AMOUNT_OF_PARAMETERS); + if (arguments.length < EXPECTED_AMOUNT_OF_PARAMETERS) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindTaskCommand.MESSAGE_USAGE)); + } + + String filterCategory = arguments[INDEX_OF_FILTER_CATEGORY]; + if (!validCategories.contains(filterCategory)) { + throw new ParseException(String.format(MESSAGE_INVALID_FILTER_CATEGORY, FindTaskCommand.MESSAGE_USAGE)); + } + + String inputType = arguments[INDEX_OF_INPUT_TYPE]; + if (!validInputTypes.contains(inputType)) { + throw new ParseException(String.format(MESSAGE_INVALID_INPUT_TYPES, FindTaskCommand.MESSAGE_USAGE)); + } + + String[] keywords = arguments[INDEX_OF_KEYWORDS].split("\\s+"); + + try { + switch (filterCategory) { + case CATEGORY_MONTH: + keywords = parseMonthKeywords(inputType, keywords); + break; + default: + assert (false); // should never be called + } + } catch (DateTimeParseException dtpe) { + throw new ParseException(MESSAGE_INVALID_KEYWORD_GIVEN); + } catch (InvalidBoundariesException ibe) { + throw new ParseException(MESSAGE_INVALID_MONTH_RANGE_FORMAT); + } + return new FindTaskCommand(filterCategory, keywords); + } + + /** + * Parses month keywords into the required form for the purpose of creating a FindTaskCommand + * @throws ParseException if the given input type is not recognized. + * @throws DateTimeParseException if any of the keywords given is an invalid month + * @throws InvalidBoundariesException if the given keywords are invalid boundary values + */ + private String[] parseMonthKeywords(String inputType, String[] keywords) throws DateTimeParseException, + InvalidBoundariesException { + int[] months; + String[] convertedKeywords = NaturalLanguageIdentifier.getInstance() + .convertNaturalLanguagesIntoMonths(keywords); + months = parseMonthsAsIntegers(convertedKeywords); + if (inputType.equals(INPUT_TYPE_BETWEEN)) { + if (!hasValidMonthBoundaries(months)) { + throw new InvalidBoundariesException(); + } + months = getAllMonthsBetweenBoundaries(months[INDEX_OF_FIRST_KEYWORD], months[INDEX_OF_SECOND_KEYWORD]); + } + convertedKeywords = convertIntoStrings(months); + return convertedKeywords; + } + + /** + * Converts an array of integer into an array of String with the same value. + */ + private String[] convertIntoStrings(int[] integers) { + String[] strings = new String[integers.length]; + for (int i = 0; i < integers.length; i++) { + strings[i] = Integer.toString(integers[i]); + } + return strings; + } + + /** + * Returns all months given two month boundaries. + */ + private int[] getAllMonthsBetweenBoundaries(int lowerBoundary, int upperBoundary) { + int monthDifference; + int[] monthsWithinRange; + + if (lowerBoundary < upperBoundary) { + monthDifference = upperBoundary - lowerBoundary + 1; + monthsWithinRange = new int[monthDifference]; + for (int i = 0; i < monthDifference; i++) { + monthsWithinRange[i] = lowerBoundary + i; + } + } else { + monthDifference = upperBoundary + AMOUNT_OF_MONTHS + 1 - lowerBoundary; + monthsWithinRange = new int[monthDifference]; + for (int i = 0; i < monthDifference; i++) { + if (lowerBoundary + i <= AMOUNT_OF_MONTHS) { + monthsWithinRange[i] = lowerBoundary + i; + } else { + monthsWithinRange[i] = lowerBoundary + i - AMOUNT_OF_MONTHS; + } + } + } + return monthsWithinRange; + } + + /** + * Returns true if the given months are valid boundaries. + */ + private boolean hasValidMonthBoundaries(int[] months) { + return months.length == REQUIRED_AMOUNT_OF_BOUNDARIES + && months[INDEX_OF_FIRST_KEYWORD] != months[INDEX_OF_SECOND_KEYWORD]; + } + + /** + * Parses given {@code String[]} of months into their integer representation. + * @throws DateTimeParseException if any of the given month is invalid. + */ + private int[] parseMonthsAsIntegers(String[] keywords) throws DateTimeParseException { + int[] months = new int[keywords.length]; + for (int i = 0; i < keywords.length; i++) { + months[i] = parseMonthAsInteger(keywords[i]); + } + return months; + } + + /** + * Parses given {@code String} of month into its integer representation. + * @throws DateTimeParseException if the given month is invalid. + */ + private int parseMonthAsInteger(String monthString) throws DateTimeParseException { + TemporalAccessor accessor; + int month = INVALID_MONTH; + if (monthString.length() < MONTH_WITH_MM_FORMAT_CHARACTER_LENGTH) { + checkMonthWithMFormat(monthString); + accessor = FORMATTER_MONTH_MM.parse("0" + monthString); + month = accessor.get(ChronoField.MONTH_OF_YEAR); + } else if (monthString.length() == MONTH_WITH_MM_FORMAT_CHARACTER_LENGTH) { + checkMonthWithMmFormat(monthString); + accessor = FORMATTER_MONTH_MM.parse(monthString); + month = accessor.get(ChronoField.MONTH_OF_YEAR); + } else if (monthString.length() == MONTH_WITH_MMM_FORMAT_CHARACTER_LENGTH) { + accessor = FORMATTER_MONTH_MMM.parse(monthString); + month = accessor.get(ChronoField.MONTH_OF_YEAR); + } else if (monthString.length() > MONTH_WITH_MMM_FORMAT_CHARACTER_LENGTH) { + accessor = FORMATTER_MONTH_MMMM.parse(monthString); + month = accessor.get(ChronoField.MONTH_OF_YEAR); + } + return month; + } + + /** + * Checks whether a given month is a valid month with m format + * @param monthString the month provided by user input + * @throws DateTimeParseException if the given month is invalid. + */ + private void checkMonthWithMFormat(String monthString) throws DateTimeParseException { + try { + int tempMonth = Integer.parseInt(monthString); + if (tempMonth <= INVALID_MONTH) { + throw new DateTimeParseException(MESSAGE_INVALID_DATE_TIME, monthString, 0); + } + } catch (NumberFormatException nfe) { + throw new DateTimeParseException(MESSAGE_INVALID_DATE_TIME, monthString, 0); + } + } + + /** + * Checks whether a given month is a valid month with mm format + * @param monthString the month provided by user input + * @throws DateTimeParseException if the given month is invalid. + */ + private void checkMonthWithMmFormat(String monthString) throws DateTimeParseException { + try { + int tempMonth = Integer.parseInt(monthString); + if (tempMonth > AMOUNT_OF_MONTHS) { + throw new DateTimeParseException(MESSAGE_INVALID_DATE_TIME, monthString, 0); + } + } catch (NumberFormatException nfe) { + throw new DateTimeParseException(MESSAGE_INVALID_DATE_TIME, monthString, 0); + } + } +} diff --git a/src/main/java/seedu/address/logic/parser/NaturalLanguageIdentifier.java b/src/main/java/seedu/address/logic/parser/NaturalLanguageIdentifier.java new file mode 100644 index 000000000000..6d4cfc92af38 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/NaturalLanguageIdentifier.java @@ -0,0 +1,120 @@ +package seedu.address.logic.parser; + +import static java.util.Objects.requireNonNull; + +import java.time.LocalDateTime; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +//@@author yungyung04 +/** + * Provides utilities to recognize and translate natural language from user input into processable values + */ +public class NaturalLanguageIdentifier { + public static final String NATURAL_NOW = "now"; + public static final String NATURAL_TODAY = "today"; + public static final String NATURAL_CURRENT = "current"; + public static final String NATURAL_LAST = "last"; + public static final String NATURAL_THIS = "this"; + public static final String NATURAL_NEXT = "next"; + public static final String NATURAL_MONTH = "month"; + public static final String NATURAL_LAST_MONTH = NATURAL_LAST + " " + NATURAL_MONTH; + public static final String NATURAL_THIS_MONTH = NATURAL_THIS + " " + NATURAL_MONTH; + public static final String NATURAL_NEXT_MONTH = NATURAL_NEXT + " " + NATURAL_MONTH; + public static final String NATURAL_CURRENT_MONTH = NATURAL_CURRENT + " " + NATURAL_MONTH; + + private static List twoWordedNaturalLanguages = new ArrayList<>(Arrays.asList( + NATURAL_LAST_MONTH, NATURAL_THIS_MONTH, NATURAL_NEXT_MONTH, NATURAL_CURRENT_MONTH)); + + private static NaturalLanguageIdentifier naturalLanguageIdentifier = null; + private LocalDateTime currentDateTime = null; + + /** + * Constructs a NaturalLanguageIdentifier object which stores the current date and time. + */ + private NaturalLanguageIdentifier() { + currentDateTime = LocalDateTime.now(); + } + + /** + * Returns an instance of NaturalLanguageIdentifier object + */ + public static NaturalLanguageIdentifier getInstance() { + if (naturalLanguageIdentifier == null) { + naturalLanguageIdentifier = new NaturalLanguageIdentifier(); + } + return naturalLanguageIdentifier; + } + + /** + * Converts any keywords that are recognizable as month-related natural languages into their month representation. + */ + public String[] convertNaturalLanguagesIntoMonths(String[] keywords) { + requireNonNull(keywords); + String[] mergedKeywords = mergeTwoWordedNaturalLanguage(keywords); + for (int i = 0; i < mergedKeywords.length; i++) { + mergedKeywords[i] = getMonthAsString(mergedKeywords[i]); + } + return mergedKeywords; + } + + /** + * Converts natural language into its month representation if possible. + */ + public String getMonthAsString(String userInput) { + requireNonNull(userInput); + String result; + switch (userInput) { + case NATURAL_TODAY: + //Fallthrough + case NATURAL_NOW: + //Fallthrough + case NATURAL_CURRENT_MONTH: + //Fallthrough + case NATURAL_THIS_MONTH: + result = currentDateTime.getMonth().name(); + break; + case NATURAL_LAST_MONTH: + result = currentDateTime.minusMonths(1).getMonth().name(); + break; + case NATURAL_NEXT_MONTH: + result = currentDateTime.plusMonths(1).getMonth().name(); + break; + default: + result = userInput; + } + return result; + } + + /** + * Merges 2 adjoin Strings if the merged form is a valid natural language. + * Keywords are case-sensitive. + */ + public static String[] mergeTwoWordedNaturalLanguage(String[] keywords) { + requireNonNull(keywords); + if (keywords.length <= 1) { + return keywords; + } + + ArrayList mergedKeywords = new ArrayList<>(); + for (int i = 0; i < keywords.length; i++) { + if (i < (keywords.length - 1) && isMergeable(keywords[i], keywords[i + 1])) { + mergedKeywords.add(keywords[i] + " " + keywords[i + 1]); + i++; + } else { + mergedKeywords.add(keywords[i]); + } + } + return mergedKeywords.toArray(new String[mergedKeywords.size()]); + } + + /** + * Checks whether 2 given words can form a valid natural language. + */ + private static boolean isMergeable(String prefix, String suffix) { + return twoWordedNaturalLanguages.contains(prefix + " " + suffix); + } + +} diff --git a/src/main/java/seedu/address/logic/parser/ParserUtil.java b/src/main/java/seedu/address/logic/parser/ParserUtil.java index 5d6d4ae3f7b1..ae8d2a36590f 100644 --- a/src/main/java/seedu/address/logic/parser/ParserUtil.java +++ b/src/main/java/seedu/address/logic/parser/ParserUtil.java @@ -1,7 +1,12 @@ package seedu.address.logic.parser; import static java.util.Objects.requireNonNull; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_DURATION; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; +import java.time.format.ResolverStyle; import java.util.Collection; import java.util.HashSet; import java.util.Optional; @@ -10,11 +15,18 @@ import seedu.address.commons.core.index.Index; import seedu.address.commons.exceptions.IllegalValueException; import seedu.address.commons.util.StringUtil; +import seedu.address.logic.commands.ChangeCommand; +import seedu.address.logic.parser.exceptions.DurationParseException; +import seedu.address.logic.parser.exceptions.SameTimeUnitException; import seedu.address.model.person.Address; import seedu.address.model.person.Email; import seedu.address.model.person.Name; import seedu.address.model.person.Phone; import seedu.address.model.tag.Tag; +import seedu.address.model.tutee.EducationLevel; +import seedu.address.model.tutee.Grade; +import seedu.address.model.tutee.School; +import seedu.address.model.tutee.Subject; /** * Contains utility methods used for parsing strings in the various *Parser classes. @@ -28,7 +40,12 @@ public class ParserUtil { public static final String MESSAGE_INVALID_INDEX = "Index is not a non-zero unsigned integer."; - public static final String MESSAGE_INSUFFICIENT_PARTS = "Number of parts must be more than 1."; + public static final String MESSAGE_INVALID_TAG = "%1$s tag is only for tutee."; + + private static final String EMPTY_STRING = ""; + private static final String TUTEE_TAG_NAME = "Tutee"; + private static final String ZERO_DURATION_FIRST_FORMAT = "0h0m"; + private static final String ZERO_DURATION_SECOND_FORMAT = "0h00m"; /** * Parses {@code oneBasedIndex} into an {@code Index} and returns it. Leading and trailing whitespaces will be @@ -155,14 +172,240 @@ public static Tag parseTag(String tag) throws IllegalValueException { } /** - * Parses {@code Collection tags} into a {@code Set}. + * Parses a {@code Collection tags} into a {@code Set}. */ public static Set parseTags(Collection tags) throws IllegalValueException { requireNonNull(tags); final Set tagSet = new HashSet<>(); for (String tagName : tags) { + if (isTuteeTag(tagName)) { + tagName = TUTEE_TAG_NAME; + } + tagSet.add(parseTag(tagName)); + } + return tagSet; + } + + //@@author ChoChihTun + /** + * Parses a person's {@code Collection tags} into a {@code Set}. + */ + public static Set parsePersonTags(Collection tags) throws IllegalValueException { + requireNonNull(tags); + final Set tagSet = new HashSet<>(); + for (String tagName : tags) { + // a person should not have tutee tag + if (isTuteeTag(tagName)) { + throw new IllegalValueException(String.format(MESSAGE_INVALID_TAG, tagName)); + } tagSet.add(parseTag(tagName)); } return tagSet; } + + /** + * Parses a tutee's {@code Collection tags} into a {@code Set}. + */ + public static Set parseTuteeTags(Collection tags) throws IllegalValueException { + requireNonNull(tags); + final Set tagSet = new HashSet<>(); + for (String tagName : tags) { + // Tutee tag is added automatically by the Tutee constructor + if (!isTuteeTag(tagName)) { + tagSet.add(parseTag(tagName)); + } + } + return tagSet; + } + + /** + * Checks if {@code String tagName} is tutee tag name + * + * @param tagName to be checked + * @return true if tagName is tutee tag name + * false if tagName is not tutee tag name + */ + private static boolean isTuteeTag(String tagName) { + return tagName.toLowerCase().equals(TUTEE_TAG_NAME.toLowerCase()); + } + + /** + * Parses a {@code String subject} into an {@code Subject}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws IllegalValueException if the given {@code subject} is invalid. + */ + public static Subject parseSubject(String subject) throws IllegalValueException { + requireNonNull(subject); + String trimmedSubject = subject.trim(); + if (!Subject.isValidSubject(trimmedSubject)) { + throw new IllegalValueException(Subject.MESSAGE_SUBJECT_CONSTRAINTS); + } + return new Subject(trimmedSubject); + } + + /** + * Parses a {@code Optional subject} into an {@code Optional} if {@code subject} is present. + * See header comment of this class regarding the use of {@code Optional} parameters. + */ + public static Optional parseSubject(Optional subject) throws IllegalValueException { + requireNonNull(subject); + return subject.isPresent() ? Optional.of(parseSubject(subject.get())) : Optional.empty(); + } + + /** + * Parses a {@code String educationLevel} into an {@code EducationLevel}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws IllegalValueException if the given {@code educationLevel} is invalid. + */ + public static EducationLevel parseEducationLevel(String educationLevel) throws IllegalValueException { + requireNonNull(educationLevel); + String trimmedEducationLevel = educationLevel.trim(); + if (!EducationLevel.isValidEducationLevel(trimmedEducationLevel)) { + throw new IllegalValueException(EducationLevel.MESSAGE_EDUCATION_LEVEL_CONSTRAINTS); + } + return new EducationLevel(trimmedEducationLevel); + } + + /** + * Parses a {@code Optional educationLevel} into an {@code Optional} + * if {@code educationLevel} is present. + * See header comment of this class regarding the use of {@code Optional} parameters. + */ + public static Optional parseEducationLevel(Optional educationLevel) + throws IllegalValueException { + requireNonNull(educationLevel); + return educationLevel.isPresent() ? Optional.of(parseEducationLevel(educationLevel.get())) : Optional.empty(); + } + + /** + * Parses a {@code String school} into an {@code School}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws IllegalValueException if the given {@code school} is invalid. + */ + public static School parseSchool(String school) throws IllegalValueException { + requireNonNull(school); + String trimmedSchool = school.trim(); + if (!School.isValidSchool(trimmedSchool)) { + throw new IllegalValueException(School.MESSAGE_SCHOOL_CONSTRAINTS); + } + return new School(trimmedSchool); + } + + /** + * Parses a {@code Optional school} into an {@code Optional} if {@code school} is present. + * See header comment of this class regarding the use of {@code Optional} parameters. + */ + public static Optional parseSchool(Optional school) throws IllegalValueException { + requireNonNull(school); + return school.isPresent() ? Optional.of(parseSchool(school.get())) : Optional.empty(); + } + + /** + * Parses a {@code String grade} into an {@code Grade}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws IllegalValueException if the given {@code grade} is invalid. + */ + public static Grade parseGrade(String grade) throws IllegalValueException { + requireNonNull(grade); + String trimmedGrade = grade.trim(); + if (!Grade.isValidGrade(trimmedGrade)) { + throw new IllegalValueException(Grade.MESSAGE_GRADE_CONSTRAINTS); + } + return new Grade(trimmedGrade); + } + + /** + * Parses a {@code Optional grade} into an {@code Optional} if {@code grade} is present. + * See header comment of this class regarding the use of {@code Optional} parameters. + */ + public static Optional parseGrade(Optional grade) throws IllegalValueException { + requireNonNull(grade); + return grade.isPresent() ? Optional.of(parseGrade(grade.get())) : Optional.empty(); + } + + /** + * Parses a {@code String timeUnit} into an {@code String} and returns it. + * Leading and trailing whitespaces will be trimmed. + * + * @throws IllegalValueException if the given {@code timeUnit} is invalid. + */ + public static String parseTimeUnit(String timeUnit) throws IllegalValueException, SameTimeUnitException { + requireNonNull(timeUnit); + String trimmedTimeUnit = timeUnit.trim(); + if (!ChangeCommandParser.isValidTimeUnit(trimmedTimeUnit)) { + throw new IllegalValueException(ChangeCommand.MESSAGE_CONSTRAINT); + } + if (ChangeCommandParser.isTimeUnitClash(trimmedTimeUnit)) { + throw new SameTimeUnitException(ChangeCommand.MESSAGE_SAME_VIEW); + } + return trimmedTimeUnit; + } + + //@@author yungyung04 + /** + * Parses a {@code String dateTime} into an {@code LocalDateTime}. + * + * @throws DateTimeParseException if the given {@code stringDateTime} is invalid. + */ + public static LocalDateTime parseDateTime(String stringDateTime) throws DateTimeParseException { + requireNonNull(stringDateTime); + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd/MM/uuuu HH:mm") + .withResolverStyle(ResolverStyle.STRICT); + return LocalDateTime.parse(stringDateTime, formatter); + } + + /** + * Returns a valid duration + * + * @throws DurationParseException if the given {@code duration} is invalid. + */ + public static String parseDuration(String duration) throws DurationParseException { + requireNonNull(duration); + if (!isValidDuration(duration)) { + throw new DurationParseException(MESSAGE_INVALID_DURATION); + } + return duration; + } + + /** + * Returns true if the given duration is valid. + */ + private static boolean isValidDuration(String duration) { + String durationValidationRegex = "([0-9]|1[0-9]|2[0-3])h([0-5][0-9]|[0-9])m"; + return duration.matches(durationValidationRegex) && !duration.equals(ZERO_DURATION_FIRST_FORMAT) + && !duration.equals(ZERO_DURATION_SECOND_FORMAT); + } + + /** + * Returns a valid task description. + * If description does not exist, returns an empty String. + */ + public static String parseDescription(String[] userInputs, int numberOfParametersWhenDescriptionExist) { + requireNonNull(userInputs); + requireNonNull(numberOfParametersWhenDescriptionExist); + if (isEmptyDescription(userInputs, numberOfParametersWhenDescriptionExist)) { + return EMPTY_STRING; + } else { + String description = getLastElement(userInputs); + return description; + } + } + + /** + * Returns the last element of an array of Strings. + */ + private static String getLastElement(String[] userInputs) { + return userInputs[userInputs.length - 1]; + } + + /** + * Returns true if the given task arguments contain a task description. + */ + private static boolean isEmptyDescription(String[] arguments, int numberOfParametersWhenDescriptionExist) { + return arguments.length < numberOfParametersWhenDescriptionExist; + } } diff --git a/src/main/java/seedu/address/logic/parser/SortPersonCommandParser.java b/src/main/java/seedu/address/logic/parser/SortPersonCommandParser.java new file mode 100644 index 000000000000..ec317cb46120 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/SortPersonCommandParser.java @@ -0,0 +1,45 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_SORTER_CATEGORY; +import static seedu.address.model.person.PersonSortUtil.CATEGORY_EDUCATION_LEVEL; +import static seedu.address.model.person.PersonSortUtil.CATEGORY_GRADE; +import static seedu.address.model.person.PersonSortUtil.CATEGORY_NAME; +import static seedu.address.model.person.PersonSortUtil.CATEGORY_SCHOOL; +import static seedu.address.model.person.PersonSortUtil.CATEGORY_SUBJECT; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import seedu.address.logic.commands.SortPersonCommand; +import seedu.address.logic.parser.exceptions.ParseException; + +/** + * Parses input arguments and creates a new SortPersonCommand object + */ +public class SortPersonCommandParser implements Parser { + + private static final String SORT_CATEGORY_VALIDATION_REGEX = "\\p{Alpha}+"; + + private List validCategories = + new ArrayList<>(Arrays.asList(CATEGORY_NAME, CATEGORY_EDUCATION_LEVEL, + CATEGORY_GRADE, CATEGORY_SCHOOL, CATEGORY_SUBJECT)); + + /** + * Parses the given {@code String} of arguments in the context of the SortPersonCommand + * and returns a SortPersonCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public SortPersonCommand parse(String args) throws ParseException { + String sortCategory = args.trim().toLowerCase(); + + if (!sortCategory.matches(SORT_CATEGORY_VALIDATION_REGEX)) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, SortPersonCommand.MESSAGE_USAGE)); + } + if (!validCategories.contains(sortCategory)) { + throw new ParseException(String.format(MESSAGE_INVALID_SORTER_CATEGORY, SortPersonCommand.MESSAGE_USAGE)); + } + return new SortPersonCommand(sortCategory); + } +} diff --git a/src/main/java/seedu/address/logic/parser/SortTaskCommandParser.java b/src/main/java/seedu/address/logic/parser/SortTaskCommandParser.java new file mode 100644 index 000000000000..3473359f7bd6 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/SortTaskCommandParser.java @@ -0,0 +1,43 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_SORTER_CATEGORY; +import static seedu.address.model.task.TaskSortUtil.CATEGORY_DATE_TIME; +import static seedu.address.model.task.TaskSortUtil.CATEGORY_MONTH; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import seedu.address.logic.commands.SortPersonCommand; +import seedu.address.logic.commands.SortTaskCommand; +import seedu.address.logic.parser.exceptions.ParseException; + +//@@author yungyung04 +/** + * Parses input arguments and creates a new SortTaskCommand object + */ +public class SortTaskCommandParser implements Parser { + + private static final String SORT_CATEGORY_VALIDATION_REGEX = "\\p{Alpha}+"; + + private List validCategories = + new ArrayList<>(Arrays.asList(CATEGORY_MONTH, CATEGORY_DATE_TIME)); + + /** + * Parses the given {@code String} of arguments in the context of the SortTaskCommand + * and returns a SortPersonCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public SortTaskCommand parse(String args) throws ParseException { + String sortCategory = args.trim().toLowerCase(); + + if (!sortCategory.matches(SORT_CATEGORY_VALIDATION_REGEX)) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, SortPersonCommand.MESSAGE_USAGE)); + } + if (!validCategories.contains(sortCategory)) { + throw new ParseException(String.format(MESSAGE_INVALID_SORTER_CATEGORY, SortPersonCommand.MESSAGE_USAGE)); + } + return new SortTaskCommand(sortCategory); + } +} diff --git a/src/main/java/seedu/address/logic/parser/exceptions/DurationParseException.java b/src/main/java/seedu/address/logic/parser/exceptions/DurationParseException.java new file mode 100644 index 000000000000..54864639ccaf --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/exceptions/DurationParseException.java @@ -0,0 +1,11 @@ +package seedu.address.logic.parser.exceptions; + +//@@author ChoChihTun +/** + * Signals that the input duration format is invalid + */ +public class DurationParseException extends Exception { + public DurationParseException(String message) { + super(message); + } +} diff --git a/src/main/java/seedu/address/logic/parser/exceptions/InvalidBoundariesException.java b/src/main/java/seedu/address/logic/parser/exceptions/InvalidBoundariesException.java new file mode 100644 index 000000000000..b0a44793b672 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/exceptions/InvalidBoundariesException.java @@ -0,0 +1,10 @@ +package seedu.address.logic.parser.exceptions; + + +//@@author yungyung04 +/** + * Signals that the given keywords cannot serve as valid boundaries + */ +public class InvalidBoundariesException extends Exception { + public InvalidBoundariesException() {}; +} diff --git a/src/main/java/seedu/address/logic/parser/exceptions/SameTimeUnitException.java b/src/main/java/seedu/address/logic/parser/exceptions/SameTimeUnitException.java new file mode 100644 index 000000000000..b97f1f1e0a17 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/exceptions/SameTimeUnitException.java @@ -0,0 +1,11 @@ +package seedu.address.logic.parser.exceptions; + +//@@author ChoChihTun +/** + * Signals that the input calendar view page time unit clashes with current time unit + */ +public class SameTimeUnitException extends Exception { + public SameTimeUnitException(String message) { + super(message); + } +} diff --git a/src/main/java/seedu/address/model/AddressBook.java b/src/main/java/seedu/address/model/AddressBook.java index f8d0260de159..bffb170a9f37 100644 --- a/src/main/java/seedu/address/model/AddressBook.java +++ b/src/main/java/seedu/address/model/AddressBook.java @@ -17,6 +17,9 @@ import seedu.address.model.person.exceptions.PersonNotFoundException; import seedu.address.model.tag.Tag; import seedu.address.model.tag.UniqueTagList; +import seedu.address.model.task.exceptions.TaskNotFoundException; +import seedu.address.model.task.exceptions.TimingClashException; +import seedu.address.model.tutee.Tutee; /** * Wraps all data at the address-book level @@ -26,6 +29,7 @@ public class AddressBook implements ReadOnlyAddressBook { private final UniquePersonList persons; private final UniqueTagList tags; + private final UniqueTaskList tasks; /* * The 'unusual' code block below is an non-static initialization block, sometimes used to avoid duplication @@ -37,6 +41,7 @@ public class AddressBook implements ReadOnlyAddressBook { { persons = new UniquePersonList(); tags = new UniqueTagList(); + tasks = new UniqueTaskList(); } public AddressBook() {} @@ -59,20 +64,34 @@ public void setTags(Set tags) { this.tags.setTags(tags); } + //@@author a-shakra + public void setTasks(List tasks) throws TimingClashException { + this.tasks.setTasks(tasks); + } + //@@author /** * Resets the existing data of this {@code AddressBook} with {@code newData}. + * ---Not modified for tasks yet!!!!!! */ public void resetData(ReadOnlyAddressBook newData) { requireNonNull(newData); setTags(new HashSet<>(newData.getTagList())); + List taskList = newData.getTaskList().stream().collect(Collectors.toList()); List syncedPersonList = newData.getPersonList().stream() .map(this::syncWithMasterTagList) .collect(Collectors.toList()); - + try { + setTasks(taskList); + } catch (TimingClashException e) { + throw new AssertionError("Timing Clash"); + } try { setPersons(syncedPersonList); + setTasks(taskList); } catch (DuplicatePersonException e) { throw new AssertionError("AddressBooks should not have duplicate persons"); + } catch (TimingClashException e) { + throw new AssertionError("AddressBooks should not have clashed tasks"); } } @@ -114,6 +133,16 @@ public void updatePerson(Person target, Person editedPerson) persons.setPerson(target, syncedEditedPerson); } + //@@author a-shakra + /** + * Adds a task to the address book. + * + */ + public void addTask(Task t) throws TimingClashException { + tasks.add(t); + } + + //@@author /** * Updates the master tag list to include tags in {@code person} that are not in the list. * @return a copy of this {@code person} such that every tag in this person points to a Tag object in the master @@ -131,10 +160,46 @@ private Person syncWithMasterTagList(Person person) { // Rebuild the list of person tags to point to the relevant tags in the master tag list. final Set correctTagReferences = new HashSet<>(); personTags.forEach(tag -> correctTagReferences.add(masterTagObjects.get(tag))); - return new Person( - person.getName(), person.getPhone(), person.getEmail(), person.getAddress(), correctTagReferences); + + if (person instanceof Tutee) { + return new Tutee(person.getName(), person.getPhone(), person.getEmail(), person.getAddress(), ( + (Tutee) person).getSubject(), ((Tutee) person).getGrade(), ( + (Tutee) person).getEducationLevel(), ( + (Tutee) person).getSchool(), correctTagReferences); + } else { + return new Person( + person.getName(), person.getPhone(), person.getEmail(), person.getAddress(), correctTagReferences); + } } + //@@author yungyung04 + /** + * Removes {@code Tag} from a particular {@code Person}. + * @throws PersonNotFoundException if {@code Person} does not exist. + */ + public void removeTagFromPerson(Tag tag, Person person) { + Set newTags = new HashSet<>(person.getTags()); + + if (!newTags.remove(tag)) { + return; + } + + if (tag.tagName.equals("Tutee")) { + return; + } + + Person newPerson = + new Person(person.getName(), person.getPhone(), person.getEmail(), person.getAddress(), newTags); + + try { + updatePerson(person, newPerson); + } catch (DuplicatePersonException dpe) { + throw new AssertionError("tag modification should not cause duplicate person"); + } catch (PersonNotFoundException pnfe) { + throw new AssertionError("target person does not exist."); + } + } + //author /** * Removes {@code key} from this {@code AddressBook}. * @throws PersonNotFoundException if the {@code key} is not in this {@code AddressBook}. @@ -147,6 +212,20 @@ public boolean removePerson(Person key) throws PersonNotFoundException { } } + //@@author a-shakra + /** + * Removes a task from the address book. + * + */ + public boolean removeTask(Task key) throws TaskNotFoundException { + if (tasks.remove(key)) { + return true; + } else { + System.out.println("Didn't work"); + return false; + } + } + //@@author //// tag-level operations public void addTag(Tag t) throws UniqueTagList.DuplicateTagException { @@ -157,10 +236,16 @@ public void addTag(Tag t) throws UniqueTagList.DuplicateTagException { @Override public String toString() { - return persons.asObservableList().size() + " persons, " + tags.asObservableList().size() + " tags"; + return persons.asObservableList().size() + " persons, " + tags.asObservableList().size() + " tags, " + + tasks.asObservableList().size() + " tasks"; // TODO: refine later } + @Override + public ObservableList getTaskList() { + return tasks.asObservableList(); + } + @Override public ObservableList getPersonList() { return persons.asObservableList(); @@ -176,6 +261,7 @@ public boolean equals(Object other) { return other == this // short circuit if same object || (other instanceof AddressBook // instanceof handles nulls && this.persons.equals(((AddressBook) other).persons) + && this.tasks.equals(((AddressBook) other).tasks) && this.tags.equalsOrderInsensitive(((AddressBook) other).tags)); } diff --git a/src/main/java/seedu/address/model/Model.java b/src/main/java/seedu/address/model/Model.java index 4a6079ce0199..f94de89d819e 100644 --- a/src/main/java/seedu/address/model/Model.java +++ b/src/main/java/seedu/address/model/Model.java @@ -1,11 +1,15 @@ package seedu.address.model; +import java.util.Comparator; import java.util.function.Predicate; import javafx.collections.ObservableList; import seedu.address.model.person.Person; import seedu.address.model.person.exceptions.DuplicatePersonException; import seedu.address.model.person.exceptions.PersonNotFoundException; +import seedu.address.model.task.exceptions.TaskNotFoundException; +import seedu.address.model.task.exceptions.TimingClashException; +import seedu.address.model.tutee.Tutee; /** * The API of the Model component. @@ -13,6 +17,10 @@ public interface Model { /** {@code Predicate} that always evaluate to true */ Predicate PREDICATE_SHOW_ALL_PERSONS = unused -> true; + Predicate PREDICATE_SHOW_ALL_TASKS = unused -> true; + + /** {@code Predicate} that evaluates to true if a parent object stores an instance of the subclass object*/ + Predicate PREDICATE_SHOW_ALL_TUTEES = person -> person instanceof Tutee; /** Clears existing backing model and replaces with the provided new data. */ void resetData(ReadOnlyAddressBook newData); @@ -45,4 +53,22 @@ void updatePerson(Person target, Person editedPerson) */ void updateFilteredPersonList(Predicate predicate); + + void addTask (Task target) throws TimingClashException; + + void deleteTask(Task target) throws TaskNotFoundException; + + ObservableList getFilteredTaskList(); + + void updateFilteredTaskList(Predicate predicate); + + /** + * Sorts the list of person according to the given comparator + */ + void sortFilteredPersonList (Comparator comparator); + + /** + * Sorts the list of tasks according to the given comparator + */ + void sortFilteredTaskList (Comparator comparator); } diff --git a/src/main/java/seedu/address/model/ModelManager.java b/src/main/java/seedu/address/model/ModelManager.java index 22a7d0eb3f4d..d14b5436ecc7 100644 --- a/src/main/java/seedu/address/model/ModelManager.java +++ b/src/main/java/seedu/address/model/ModelManager.java @@ -3,18 +3,22 @@ import static java.util.Objects.requireNonNull; import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; +import java.util.Comparator; import java.util.function.Predicate; import java.util.logging.Logger; import javafx.collections.FXCollections; import javafx.collections.ObservableList; import javafx.collections.transformation.FilteredList; +import javafx.collections.transformation.SortedList; import seedu.address.commons.core.ComponentManager; import seedu.address.commons.core.LogsCenter; import seedu.address.commons.events.model.AddressBookChangedEvent; import seedu.address.model.person.Person; import seedu.address.model.person.exceptions.DuplicatePersonException; import seedu.address.model.person.exceptions.PersonNotFoundException; +import seedu.address.model.task.exceptions.TaskNotFoundException; +import seedu.address.model.task.exceptions.TimingClashException; /** * Represents the in-memory model of the address book data. @@ -24,7 +28,10 @@ public class ModelManager extends ComponentManager implements Model { private static final Logger logger = LogsCenter.getLogger(ModelManager.class); private final AddressBook addressBook; - private final FilteredList filteredPersons; + private FilteredList filteredPersons; + private FilteredList filteredTasks; + private SortedList sortedPersons; + private SortedList sortedTasks; /** * Initializes a ModelManager with the given addressBook and userPrefs. @@ -37,6 +44,9 @@ public ModelManager(ReadOnlyAddressBook addressBook, UserPrefs userPrefs) { this.addressBook = new AddressBook(addressBook); filteredPersons = new FilteredList<>(this.addressBook.getPersonList()); + filteredTasks = new FilteredList<>(this.addressBook.getTaskList()); + sortedPersons = new SortedList<>(filteredPersons); + sortedTasks = new SortedList<>(filteredTasks); } public ModelManager() { @@ -80,8 +90,31 @@ public void updatePerson(Person target, Person editedPerson) addressBook.updatePerson(target, editedPerson); indicateAddressBookChanged(); } + //@@author a-shakra + @Override + public synchronized void addTask(Task aTask) throws TimingClashException { + addressBook.addTask(aTask); + updateFilteredTaskList(PREDICATE_SHOW_ALL_TASKS); //Change to new predicate? + indicateAddressBookChanged(); + } - //=========== Filtered Person List Accessors ============================================================= + @Override + public synchronized void deleteTask(Task target) throws TaskNotFoundException { + addressBook.removeTask(target); + indicateAddressBookChanged(); + } + + @Override + public ObservableList getFilteredTaskList() { + return FXCollections.unmodifiableObservableList(sortedTasks); + } + + @Override + public void updateFilteredTaskList(Predicate predicate) { + requireNonNull(predicate); + filteredTasks.setPredicate(predicate); + } + //@@author /** * Returns an unmodifiable view of the list of {@code Person} backed by the internal list of @@ -89,7 +122,7 @@ public void updatePerson(Person target, Person editedPerson) */ @Override public ObservableList getFilteredPersonList() { - return FXCollections.unmodifiableObservableList(filteredPersons); + return FXCollections.unmodifiableObservableList(sortedPersons); } @Override @@ -98,6 +131,18 @@ public void updateFilteredPersonList(Predicate predicate) { filteredPersons.setPredicate(predicate); } + //@@author yungyung04 + @Override + public void sortFilteredPersonList(Comparator comparator) { + sortedPersons.setComparator(comparator); + } + + @Override + public void sortFilteredTaskList(Comparator comparator) { + sortedTasks.setComparator(comparator); + } + + //@@author @Override public boolean equals(Object obj) { // short circuit if same object @@ -110,10 +155,13 @@ public boolean equals(Object obj) { return false; } + // state check ModelManager other = (ModelManager) obj; return addressBook.equals(other.addressBook) - && filteredPersons.equals(other.filteredPersons); + && filteredPersons.equals(other.filteredPersons) + && filteredTasks.equals(other.filteredTasks) + && sortedPersons.equals(other.sortedPersons) + && sortedTasks.equals(other.sortedTasks); } - } diff --git a/src/main/java/seedu/address/model/ReadOnlyAddressBook.java b/src/main/java/seedu/address/model/ReadOnlyAddressBook.java index 1f4e49a37d67..1417507258fc 100644 --- a/src/main/java/seedu/address/model/ReadOnlyAddressBook.java +++ b/src/main/java/seedu/address/model/ReadOnlyAddressBook.java @@ -20,5 +20,11 @@ public interface ReadOnlyAddressBook { * This list will not contain any duplicate tags. */ ObservableList getTagList(); - + //@@author a-shakra + /** + * Returns an unmodifiable view of the tasks list + * This list will not contain any duplicate task + */ + ObservableList getTaskList(); + //@@author } diff --git a/src/main/java/seedu/address/model/Task.java b/src/main/java/seedu/address/model/Task.java new file mode 100644 index 000000000000..91bd1eeaa678 --- /dev/null +++ b/src/main/java/seedu/address/model/Task.java @@ -0,0 +1,29 @@ +package seedu.address.model; + +import java.time.LocalDateTime; + +import com.calendarfx.model.Entry; + +/** + * Represents a task that person has + */ +public interface Task { + + String MESSAGE_DESCRIPTION_CONSTRAINTS = "Tasks Should have a non-empty description"; + + String MESSAGE_DURATION_CONSTRAINTS = "Duration must be a non-null value"; + + String MESSAGE_DATETIME_CONSTRAINTS = "Date and time must be a non-null value"; + + LocalDateTime getTaskDateTime(); + + String getStringTaskDateTime(); + + String getDescription(); + + String getDuration(); + + Entry getEntry(); + + boolean equals(Object two); +} diff --git a/src/main/java/seedu/address/model/TaskContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/TaskContainsKeywordsPredicate.java new file mode 100644 index 000000000000..d1563474e2f6 --- /dev/null +++ b/src/main/java/seedu/address/model/TaskContainsKeywordsPredicate.java @@ -0,0 +1,33 @@ +package seedu.address.model; + +import java.util.List; +import java.util.function.Predicate; + +import seedu.address.commons.util.StringUtil; + + +/** + * Tests that a {@code Task}'s {@code description} matches any of the keywords given. + */ +//@@author a-shakra +public class TaskContainsKeywordsPredicate implements Predicate { + private final List keywords; + + public TaskContainsKeywordsPredicate(List keywords) { + this.keywords = keywords; + } + + @Override + public boolean test(Task task) { + return keywords.stream() + .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(task.getDescription(), keyword)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof TaskContainsKeywordsPredicate // instanceof handles nulls + && this.keywords.equals(((TaskContainsKeywordsPredicate) other).keywords)); // state check + } + +} diff --git a/src/main/java/seedu/address/model/UniqueTaskList.java b/src/main/java/seedu/address/model/UniqueTaskList.java new file mode 100644 index 000000000000..15e746b2a3ec --- /dev/null +++ b/src/main/java/seedu/address/model/UniqueTaskList.java @@ -0,0 +1,152 @@ +package seedu.address.model; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; + +import java.time.LocalDateTime; +import java.util.Iterator; +import java.util.List; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import seedu.address.model.task.exceptions.TaskNotFoundException; +import seedu.address.model.task.exceptions.TimingClashException; + +/** + * A list of tasks that enforces uniqueness between its elements and does not allow nulls. + * + * Supports a minimal set of list operations. + * + */ +//@@author a-shakra +public class UniqueTaskList implements Iterable { + + private static final String HOUR_DELIMITER = "h"; + private static final String MINUTE_DELIMITER = "m"; + private final ObservableList internalList = FXCollections.observableArrayList(); + + /** + * Constructs empty TaskList. + */ + public UniqueTaskList() {} + + //@@author ChoChihTun + /** + * Adds a task to the list. + * + * @throws TimingClashException if there is a clash in timing with an existing task + */ + public void add(Task toAdd) throws TimingClashException { + requireNonNull(toAdd); + if (isTimeClash(toAdd.getTaskDateTime(), toAdd.getDuration())) { + throw new TimingClashException(); + } + internalList.add(toAdd); + } + + //@@author a-shakra + /** + * Replaces the person {@code target} in the list with {@code editedPerson}. + */ + public void setTask(Task target, Task editedTask) throws TaskNotFoundException { + requireNonNull(editedTask); + + int index = internalList.indexOf(target); + if (index == -1) { + throw new TaskNotFoundException(); + } + internalList.set(index, editedTask); + } + + + /** + * Removes the equivalent task from the list. + */ + public boolean remove(Task toRemove) throws TaskNotFoundException { + requireNonNull(toRemove); + final boolean taskFoundAndDeleted = internalList.remove(toRemove); + if (!taskFoundAndDeleted) { + throw new TaskNotFoundException(); + } + return taskFoundAndDeleted; + } + + public void setTasks(UniqueTaskList replacement) { + this.internalList.setAll(replacement.internalList); + } + + + public void setTasks(List tasks) throws TimingClashException { + requireAllNonNull(tasks); + final UniqueTaskList replacement = new UniqueTaskList(); + for (final Task task : tasks) { + replacement.add(task); + } + setTasks(replacement); + } + + /** + * Returns the backing list as an unmodifiable {@code ObservableList}. + */ + public ObservableList asObservableList() { + return FXCollections.unmodifiableObservableList(internalList); + } + + //@@author ChoChihTun + /** + * Checks for any clashes in the task timing in schedule + * + * @param startDateTime start date and time of new task + * @param duration duration of new task + */ + private boolean isTimeClash(LocalDateTime startDateTime, String duration) { + LocalDateTime taskEndTime = getTaskEndTime(duration, startDateTime); + + for (Task recordedTask : internalList) { + LocalDateTime startTimeOfRecordedTask = recordedTask.getTaskDateTime(); + String durationOfRecordedTask = recordedTask.getDuration(); + LocalDateTime endTimeOfRecordedTask = getTaskEndTime(durationOfRecordedTask, startTimeOfRecordedTask); + boolean isClash = !(taskEndTime.isBefore(startTimeOfRecordedTask) + || startDateTime.isAfter(endTimeOfRecordedTask)) + && !(taskEndTime.equals(startTimeOfRecordedTask) + || startDateTime.equals(endTimeOfRecordedTask)); + if (isClash) { + return true; + } + } + return false; + } + + /** + * Returns date and time when the task ends + */ + private static LocalDateTime getTaskEndTime(String duration, LocalDateTime startDateTime) { + int indexOfHourDelimiter = duration.indexOf(HOUR_DELIMITER); + int indexOfMinuteDelimiter = duration.indexOf(MINUTE_DELIMITER); + int indexOfFirstDigitInMinute = indexOfHourDelimiter + 1; + int hoursInDuration = Integer.parseInt(duration.substring(0, indexOfHourDelimiter)); + int minutesInDuration = Integer.parseInt(duration.substring(indexOfFirstDigitInMinute, indexOfMinuteDelimiter)); + + LocalDateTime taskEndTime; + taskEndTime = startDateTime.plusHours(hoursInDuration).plusMinutes(minutesInDuration); + return taskEndTime; + } + + //@@author a-shakra + @Override + public Iterator iterator() { + return internalList.iterator(); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof UniqueTaskList // instanceof handles nulls + && this.internalList.equals(((UniqueTaskList) other).internalList)); + } + + @Override + public int hashCode() { + return internalList.hashCode(); + } +} diff --git a/src/main/java/seedu/address/model/UserPrefs.java b/src/main/java/seedu/address/model/UserPrefs.java index 8c8a071876eb..34cb3dc95b76 100644 --- a/src/main/java/seedu/address/model/UserPrefs.java +++ b/src/main/java/seedu/address/model/UserPrefs.java @@ -37,10 +37,6 @@ public void setAddressBookFilePath(String addressBookFilePath) { this.addressBookFilePath = addressBookFilePath; } - public String getAddressBookName() { - return addressBookName; - } - public void setAddressBookName(String addressBookName) { this.addressBookName = addressBookName; } diff --git a/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java index 827e2cc106bd..d9f0ecb98593 100644 --- a/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java +++ b/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java @@ -18,7 +18,7 @@ public NameContainsKeywordsPredicate(List keywords) { @Override public boolean test(Person person) { return keywords.stream() - .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(person.getName().fullName, keyword)); + .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(person.getName().fullName, keyword)); } @Override diff --git a/src/main/java/seedu/address/model/person/Person.java b/src/main/java/seedu/address/model/person/Person.java index ec9f2aa5e919..2e61a948b95e 100644 --- a/src/main/java/seedu/address/model/person/Person.java +++ b/src/main/java/seedu/address/model/person/Person.java @@ -15,12 +15,11 @@ */ public class Person { - private final Name name; - private final Phone phone; - private final Email email; - private final Address address; - - private final UniqueTagList tags; + protected final Name name; + protected final Phone phone; + protected final Email email; + protected final Address address; + protected final UniqueTagList tags; /** * Every field must be present and not null. diff --git a/src/main/java/seedu/address/model/person/PersonSortUtil.java b/src/main/java/seedu/address/model/person/PersonSortUtil.java new file mode 100644 index 000000000000..a00dea35d8b9 --- /dev/null +++ b/src/main/java/seedu/address/model/person/PersonSortUtil.java @@ -0,0 +1,225 @@ +package seedu.address.model.person; + +import static java.util.Objects.requireNonNull; + +import java.util.Comparator; +import java.util.logging.Logger; + +import seedu.address.commons.core.LogsCenter; +import seedu.address.model.tutee.Tutee; + +//@@author yungyung04 +/** + * Provides utilities for sorting a list of Persons. + */ +public class PersonSortUtil { + public static final String CATEGORY_NAME = "name"; + public static final String CATEGORY_EDUCATION_LEVEL = "edu"; + public static final String CATEGORY_GRADE = "grade"; + public static final String CATEGORY_SCHOOL = "school"; + public static final String CATEGORY_SUBJECT = "subject"; + public static final int NEGATIVE_DIGIT = -1; + public static final int POSITIVE_DIGIT = 1; + + private static final Logger logger = LogsCenter.getLogger(PersonSortUtil.class); + + /** + * Returns the appropriate Person comparator given the sorting category. + */ + public static Comparator getComparator(String sortCategory) { + requireNonNull(sortCategory); + Comparator comparator = null; + + switch (sortCategory) { + case CATEGORY_NAME: + comparator = getNameComparator(); + break; + case CATEGORY_EDUCATION_LEVEL: + comparator = getEducationLevelComparator(); + break; + case CATEGORY_GRADE: + comparator = getGradeComparator(); + break; + case CATEGORY_SCHOOL: + comparator = getSchoolComparator(); + break; + case CATEGORY_SUBJECT: + comparator = getSubjectComparator(); + break; + default: + logger.severe("an invalid category is identified in PersonSortUtil class."); + assert (false); //invalid sortCategory should be identified in parser. + } + return comparator; + } + + /** + * Returns a comparator which is useful to sort education level of a Tutee in an increasing lexicographical order.. + * Non tutees are listed last according to their names in an increasing lexicographical order. + */ + private static Comparator getEducationLevelComparator() { + return new Comparator() { + @Override + public int compare(Person person1, Person person2) { + int result = 0; //value will be replaced + if (areBothTutees(person1, person2)) { + + String personEducationLevel1 = ((Tutee) person1).getEducationLevel().toString(); + String personEducationLevel2 = ((Tutee) person2).getEducationLevel().toString(); + + result = personEducationLevel1.compareToIgnoreCase(personEducationLevel2); + } else if (isFirstTutee(person1, person2)) { + result = NEGATIVE_DIGIT; + } else if (isSecondTutee(person1, person2)) { + result = POSITIVE_DIGIT; + } else if (areNotTutees(person1, person2)) { + result = compareNameLexicographically(person1, person2); + } else { + assert (false); //should never reach this statement -> works as safety measure + } + return result; + } + }; + } + + /** + * Returns a comparator which is useful to sort grade Tutees in an increasing lexicographical order.. + * Non tutees are listed last according to their names in an increasing lexicographical order. + */ + private static Comparator getGradeComparator() { + return new Comparator() { + @Override + public int compare(Person person1, Person person2) { + int result = 0; //value will be replaced + if (areBothTutees(person1, person2)) { + + String personGrade1 = ((Tutee) person1).getGrade().toString(); + String personGrade2 = ((Tutee) person2).getGrade().toString(); + + result = personGrade1.compareToIgnoreCase(personGrade2); + } else if (isFirstTutee(person1, person2)) { + result = NEGATIVE_DIGIT; + } else if (isSecondTutee(person1, person2)) { + result = POSITIVE_DIGIT; + } else if (areNotTutees(person1, person2)) { + result = compareNameLexicographically(person1, person2); + } else { + assert (false); //should never reach this statement + } + return result; + } + }; + } + + /** + * Returns a comparator which is useful to sort school of Tutees in an increasing lexicographical order. + * Non tutees are listed last according to their names in an increasing lexicographical order. + */ + private static Comparator getSchoolComparator() { + return new Comparator() { + @Override + public int compare(Person person1, Person person2) { + int result = 0; //value will be replaced + if (areBothTutees(person1, person2)) { + + String personSchool1 = ((Tutee) person1).getSchool().toString(); + String personSchool2 = ((Tutee) person2).getSchool().toString(); + + result = personSchool1.compareToIgnoreCase(personSchool2); + } else if (isFirstTutee(person1, person2)) { + result = NEGATIVE_DIGIT; + } else if (isSecondTutee(person1, person2)) { + result = POSITIVE_DIGIT; + } else if (areNotTutees(person1, person2)) { + result = compareNameLexicographically(person1, person2); + } else { + assert (false); //should never reach this statement + } + return result; + } + }; + } + + /** + * Returns a comparator which is useful to sort subject of Tutees in an increasing lexicographical order. + * Non tutees are listed last according to their names in an increasing lexicographical order. + */ + private static Comparator getSubjectComparator() { + return new Comparator() { + @Override + public int compare(Person person1, Person person2) { + int result = 0; //value will be replaced + if (areBothTutees(person1, person2)) { + + String personSubject1 = ((Tutee) person1).getSubject().toString(); + String personSubject2 = ((Tutee) person2).getSubject().toString(); + + result = personSubject1.compareToIgnoreCase(personSubject2); + } else if (isFirstTutee(person1, person2)) { + result = NEGATIVE_DIGIT; + } else if (isSecondTutee(person1, person2)) { + result = POSITIVE_DIGIT; + } else if (areNotTutees(person1, person2)) { + result = compareNameLexicographically(person1, person2); + } else { + assert (false); //should never reach this statement + } + return result; + } + }; + } + + /** + * Returns a comparator which is useful to sort name of Persons in an increasing lexicographical order. + */ + private static Comparator getNameComparator() { + return new Comparator() { + @Override + public int compare(Person person1, Person person2) { + return compareNameLexicographically(person1, person2); + } + }; + } + + /** + * Returns true if both the given {@code Person} are subclass of {@code Tutee} + */ + private static boolean areNotTutees(Person person1, Person person2) { + return !(person1 instanceof Tutee || person2 instanceof Tutee); + } + + /** + * Returns true if the given {@code person1} is the only subclass of {@code Tutee} + */ + private static boolean isSecondTutee(Person person1, Person person2) { + return !(person1 instanceof Tutee) && person2 instanceof Tutee; + } + + /** + * Returns true if the given {@code person2} is the only subclass of {@code Tutee} + */ + private static boolean isFirstTutee(Person person1, Person person2) { + return person1 instanceof Tutee && !(person2 instanceof Tutee); + } + + /** + * Returns true if both the given {@code Person} are not subclass of {@code Tutee} + */ + private static boolean areBothTutees(Person person1, Person person2) { + return person1 instanceof Tutee && person2 instanceof Tutee; + } + + /** + * Compares the name of 2 given persons and returns an integer according to their lexicographical relationn + * Integer returned follows the behaviour of {@code compareTo} in Java.lang.String + * + * @param person1 first person to be compared + * @param person2 second person to be compared + */ + public static int compareNameLexicographically(Person person1, Person person2) { + String personName1 = person1.getName().toString(); + String personName2 = person2.getName().toString(); + + return personName1.compareToIgnoreCase(personName2); + } +} diff --git a/src/main/java/seedu/address/model/personal/PersonalTask.java b/src/main/java/seedu/address/model/personal/PersonalTask.java new file mode 100644 index 000000000000..b3cfc9cf4a37 --- /dev/null +++ b/src/main/java/seedu/address/model/personal/PersonalTask.java @@ -0,0 +1,137 @@ +package seedu.address.model.personal; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.time.format.ResolverStyle; + +import com.calendarfx.model.Entry; +import com.calendarfx.model.Interval; + +import seedu.address.model.Task; + +//@@author ChoChihTun +/** + * Represents the personal task that the user has + */ +public class PersonalTask implements Task { + + private static final String HOUR_DELIMITER = "h"; + private static final String MINUTE_DELIMITER = "m"; + private static final String NULL_STRING = ""; + + private DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd/MM/uuuu HH:mm") + .withResolverStyle(ResolverStyle.STRICT); + private String description; + private String duration; + private LocalDateTime taskDateTime; + private Entry entry; + + /** + * Creates a personal task + * + * @param taskDateTime date and time of the task + * @param duration duration of the task + * @param description description of the task + */ + public PersonalTask(LocalDateTime taskDateTime, String duration, String description) { + this.taskDateTime = taskDateTime; + this.duration = duration; + this.description = description; + this.entry = createCalendarEntry(); + } + + /** + * Creates an entry to be entered into the calendar + * + * @return Calendar entry + */ + private Entry createCalendarEntry() { + LocalDateTime endDateTime = getTaskEndTime(); + Interval interval = new Interval(taskDateTime, endDateTime); + Entry entry = new Entry(description); + entry.setInterval(interval); + return entry; + } + + /** + * Returns the end time of the task + */ + private LocalDateTime getTaskEndTime() { + int hoursInDuration = parseHours(); + int minutesInDuration = parseMinutes(); + LocalDateTime endDateTime = taskDateTime.plusHours(hoursInDuration).plusMinutes(minutesInDuration); + return endDateTime; + } + + /** + * Parses hour component out of duration + * + * @return number of hours in the duration + */ + private int parseHours() { + int indexOfHourDelimiter = duration.indexOf(HOUR_DELIMITER); + return Integer.parseInt(duration.substring(0, indexOfHourDelimiter)); + } + + /** + * Parses minute component out of duration + * + * @return number of minutes in the duration + */ + private int parseMinutes() { + int startOfMinutesIndex = duration.indexOf(HOUR_DELIMITER) + 1; + int indexOfMinuteDelimiter = duration.indexOf(MINUTE_DELIMITER); + return Integer.parseInt(duration.substring(startOfMinutesIndex, indexOfMinuteDelimiter)); + } + + public Entry getEntry() { + return entry; + } + + public LocalDateTime getTaskDateTime() { + return taskDateTime; + } + + public String getDescription() { + return description; + } + + public String getDuration() { + return duration; + } + + @Override + public String getStringTaskDateTime() { + return taskDateTime.format(formatter); + } + + //@@author yungyung04 + @Override + public String toString() { + if (hasDescription()) { + return "Personal task with description " + description + " on " + + Integer.toString(taskDateTime.getDayOfMonth()) + " " + + taskDateTime.getMonth().name() + " " + Integer.toString(taskDateTime.getYear()); + } else { + return "Personal task without description on " + Integer.toString(taskDateTime.getDayOfMonth()) + + " " + taskDateTime.getMonth().name() + " " + Integer.toString(taskDateTime.getYear()); + } + } + + /** + * Returns true if the tuition task contains a non-empty description. + */ + private boolean hasDescription() { + return !description.equals(NULL_STRING); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof PersonalTask // instanceof handles nulls + && taskDateTime.equals(((PersonalTask) other).taskDateTime) + && duration.equals(((PersonalTask) other).duration) + && description.equals(((PersonalTask) other).description)); + } + +} diff --git a/src/main/java/seedu/address/model/task/MonthContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/task/MonthContainsKeywordsPredicate.java new file mode 100644 index 000000000000..766b04b4b50c --- /dev/null +++ b/src/main/java/seedu/address/model/task/MonthContainsKeywordsPredicate.java @@ -0,0 +1,34 @@ +package seedu.address.model.task; + +import java.util.List; +import java.util.function.Predicate; + +import seedu.address.commons.util.StringUtil; +import seedu.address.model.Task; + +//@@author yungyung04 +/** + * Tests that a {@code Task}'s month matches any of the {@code int month} given. + */ +public class MonthContainsKeywordsPredicate implements Predicate { + private final List keywords; + + public MonthContainsKeywordsPredicate(List keywords) { + this.keywords = keywords; + } + + @Override + public boolean test(Task task) { + return keywords.stream() + .anyMatch(keyword -> StringUtil.containsWordIgnoreCase( + Integer.toString(task.getTaskDateTime().getMonthValue()), keyword)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof seedu.address.model.task.MonthContainsKeywordsPredicate // instanceof handles nulls + && this.keywords + .equals(((seedu.address.model.task.MonthContainsKeywordsPredicate) other).keywords)); // state check + } +} diff --git a/src/main/java/seedu/address/model/task/TaskSortUtil.java b/src/main/java/seedu/address/model/task/TaskSortUtil.java new file mode 100644 index 000000000000..c2d8f449ff20 --- /dev/null +++ b/src/main/java/seedu/address/model/task/TaskSortUtil.java @@ -0,0 +1,95 @@ +package seedu.address.model.task; + +import java.time.LocalDateTime; +import java.util.Comparator; +import java.util.logging.Logger; + +import seedu.address.commons.core.LogsCenter; +import seedu.address.model.Task; + +//@@author yungyung04 +/** + * Provides utilities for sorting a list of Tasks. + */ +public class TaskSortUtil { + public static final String CATEGORY_DATE_TIME = "datetime"; + public static final String CATEGORY_MONTH = "month"; + public static final int NEGATIVE_DIGIT = -1; + public static final int POSITIVE_DIGIT = 1; + + private static final Logger logger = LogsCenter.getLogger(TaskSortUtil.class); + + /** + * Returns the appropriate Task comparator given the sorting category + */ + public static Comparator getComparator(String sortCategory) { + Comparator comparator = null; + + switch (sortCategory) { + case CATEGORY_MONTH: + comparator = getMonthComparator(); + break; + case CATEGORY_DATE_TIME: + comparator = getDateTimeComparator(); + break; + default: + logger.severe("an invalid category is identified in TaskSortUtil class."); + assert (false); //invalid sortCategory should be identified in parser. + } + return comparator; + } + + /** + * Returns a comparator which is useful for sorting tasks based on the month sequence in an increasing order. + */ + private static Comparator getMonthComparator() { + return new Comparator() { + @Override + public int compare(Task task1, Task task2) { + int month1 = task1.getTaskDateTime().getMonthValue(); + int month2 = task2.getTaskDateTime().getMonthValue(); + + if (month1 != month2) { + return compareByMonth(month1, month2); + } else { + return compareByTime(task1.getTaskDateTime(), task2.getTaskDateTime()); + } + } + }; + } + + /** + * Returns a comparator which is useful for sorting tasks based on the date and time sequence in increasing order. + */ + private static Comparator getDateTimeComparator() { + return new Comparator() { + @Override + public int compare(Task task1, Task task2) { + return compareByTime(task1.getTaskDateTime(), task2.getTaskDateTime()); + } + }; + } + + /** + * Compares the 2 given months and returns an integer according to their sequence in standard Gregorian calendar. + */ + private static int compareByMonth(int month1, int month2) { + if (month1 < month2) { + return NEGATIVE_DIGIT; + } else { + return POSITIVE_DIGIT; + } + } + /** + * Compares the 2 given {@code LocalDateTime} and + * Returns an integer according to their sequence in standard Gregorian calendar. + */ + private static int compareByTime(LocalDateTime dateTime1, LocalDateTime dateTime2) { + assert (!dateTime1.isEqual(dateTime2)); //time should be different due to thrown exception when task is added + if (dateTime1.isBefore(dateTime2)) { + return NEGATIVE_DIGIT; + } else { + return POSITIVE_DIGIT; + } + } +} 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 000000000000..690ed3193585 --- /dev/null +++ b/src/main/java/seedu/address/model/task/exceptions/TaskNotFoundException.java @@ -0,0 +1,7 @@ +package seedu.address.model.task.exceptions; + +//@@author yungyung04 +/** + * Signals that the operation is unable to find the specified task. + */ +public class TaskNotFoundException extends Exception {} diff --git a/src/main/java/seedu/address/model/task/exceptions/TimingClashException.java b/src/main/java/seedu/address/model/task/exceptions/TimingClashException.java new file mode 100644 index 000000000000..46892173ea91 --- /dev/null +++ b/src/main/java/seedu/address/model/task/exceptions/TimingClashException.java @@ -0,0 +1,15 @@ +package seedu.address.model.task.exceptions; + +import static seedu.address.commons.core.Messages.MESSAGE_TASK_TIMING_CLASHES; + +import seedu.address.commons.exceptions.DuplicateDataException; + +//@@author ChoChihTun +/** + * Signals that there is a clash of timing in the schedule or there is a duplicate task + */ +public class TimingClashException extends DuplicateDataException { + public TimingClashException() { + super(MESSAGE_TASK_TIMING_CLASHES); + } +} diff --git a/src/main/java/seedu/address/model/tutee/EducationLevel.java b/src/main/java/seedu/address/model/tutee/EducationLevel.java new file mode 100644 index 000000000000..23e9eda520c1 --- /dev/null +++ b/src/main/java/seedu/address/model/tutee/EducationLevel.java @@ -0,0 +1,54 @@ +package seedu.address.model.tutee; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +//@@author ChoChihTun +/** + * Represents a Tutee's education level in the address book. + * Guarantees: immutable; is valid as declared in {@link #isValidEducationLevel(String)} + */ +public class EducationLevel { + + public static final String MESSAGE_EDUCATION_LEVEL_CONSTRAINTS = + "Education level should only be either primary, secondary or junior college, and it should not be blank"; + public static final String EDUCATION_LEVEL_VALIDATION_REGEX = "(?i)\\b(primary|secondary|(junior\\scollege))\\b"; + + public final String educationLevel; + + /** + * Constructs a {@code education level}. + * + * @param educationLevel A valid education level. + */ + public EducationLevel(String educationLevel) { + requireNonNull(educationLevel); + checkArgument(isValidEducationLevel(educationLevel), MESSAGE_EDUCATION_LEVEL_CONSTRAINTS); + this.educationLevel = educationLevel; + } + + /** + * Returns true if a given string is a valid education level. + */ + public static boolean isValidEducationLevel(String test) { + return test.matches(EDUCATION_LEVEL_VALIDATION_REGEX); + } + + + @Override + public String toString() { + return educationLevel; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof EducationLevel // instanceof handles nulls + && this.educationLevel.equals(((EducationLevel) other).educationLevel)); // state check + } + + @Override + public int hashCode() { + return educationLevel.hashCode(); + } +} diff --git a/src/main/java/seedu/address/model/tutee/EducationLevelContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/tutee/EducationLevelContainsKeywordsPredicate.java new file mode 100644 index 000000000000..934fd91bacae --- /dev/null +++ b/src/main/java/seedu/address/model/tutee/EducationLevelContainsKeywordsPredicate.java @@ -0,0 +1,36 @@ +package seedu.address.model.tutee; + +import java.util.List; +import java.util.function.Predicate; + +import seedu.address.commons.util.StringUtil; +import seedu.address.model.person.Person; + +/** + * Tests that a {@code Tutee}'s {@code Education Level} matches any of the keywords given. + */ +public class EducationLevelContainsKeywordsPredicate implements Predicate { + private final List keywords; + + public EducationLevelContainsKeywordsPredicate(List keywords) { + this.keywords = keywords; + } + + @Override + public boolean test(Person person) { + if (!(person instanceof Tutee)) { + return false; + } else { + return keywords.stream() + .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(( + (Tutee) person).getEducationLevel().toString(), keyword)); + } + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof EducationLevelContainsKeywordsPredicate // instanceof handles nulls + && this.keywords.equals(((EducationLevelContainsKeywordsPredicate) other).keywords)); // state check + } +} diff --git a/src/main/java/seedu/address/model/tutee/Grade.java b/src/main/java/seedu/address/model/tutee/Grade.java new file mode 100644 index 000000000000..050cb4de9bee --- /dev/null +++ b/src/main/java/seedu/address/model/tutee/Grade.java @@ -0,0 +1,55 @@ +package seedu.address.model.tutee; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +//@@author ChoChihTun +/** + * Represents a Tutee's subject grade + * Guarantees: immutable; is valid as declared in {@link #isValidGrade(String)} + */ +public class Grade { + + public static final String MESSAGE_GRADE_CONSTRAINTS = + "Grade should start with an alphabetic character and followed by any character (ONLY ONE) or blank, " + + "and it should not be blank"; + public static final String GRADE_VALIDATION_REGEX = "[\\p{Alpha}].??"; + + public final String grade; + + /** + * Constructs a {@code Grade}. + * + * @param grade A valid grade. + */ + public Grade(String grade) { + requireNonNull(grade); + checkArgument(isValidGrade(grade), MESSAGE_GRADE_CONSTRAINTS); + this.grade = grade; + } + + /** + * Returns true if a given string is a valid grade. + */ + public static boolean isValidGrade(String test) { + return test.matches(GRADE_VALIDATION_REGEX); + } + + + @Override + public String toString() { + return grade; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof Grade // instanceof handles nulls + && this.grade.equals(((Grade) other).grade)); // state check + } + + @Override + public int hashCode() { + return grade.hashCode(); + } +} diff --git a/src/main/java/seedu/address/model/tutee/GradeContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/tutee/GradeContainsKeywordsPredicate.java new file mode 100644 index 000000000000..b91d74fb4181 --- /dev/null +++ b/src/main/java/seedu/address/model/tutee/GradeContainsKeywordsPredicate.java @@ -0,0 +1,36 @@ +package seedu.address.model.tutee; + +import java.util.List; +import java.util.function.Predicate; + +import seedu.address.commons.util.StringUtil; +import seedu.address.model.person.Person; + +/** + * Tests that a {@code Tutee}'s {@code Grade} matches any of the keywords given. + */ +public class GradeContainsKeywordsPredicate implements Predicate { + private final List keywords; + + public GradeContainsKeywordsPredicate(List keywords) { + this.keywords = keywords; + } + + @Override + public boolean test(Person person) { + if (!(person instanceof Tutee)) { + return false; + } else { + return keywords.stream() + .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(( + (Tutee) person).getGrade().toString(), keyword)); + } + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof GradeContainsKeywordsPredicate // instanceof handles nulls + && this.keywords.equals(((GradeContainsKeywordsPredicate) other).keywords)); // state check + } +} diff --git a/src/main/java/seedu/address/model/tutee/School.java b/src/main/java/seedu/address/model/tutee/School.java new file mode 100644 index 000000000000..86af3c3cdc36 --- /dev/null +++ b/src/main/java/seedu/address/model/tutee/School.java @@ -0,0 +1,54 @@ +package seedu.address.model.tutee; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +//@@author ChoChihTun +/** + * Represents a Tutee's school + * Guarantees: immutable; is valid as declared in {@link #isValidSchool(String)} + */ +public class School { + + public static final String MESSAGE_SCHOOL_CONSTRAINTS = + "School should only contain alphabetic characters and spaces, and it should not be blank"; + public static final String SCHOOL_VALIDATION_REGEX = "[\\p{Alpha}][\\p{Alpha} ]*"; + + public final String school; + + /** + * Constructs a {@code School}. + * + * @param school A valid school. + */ + public School(String school) { + requireNonNull(school); + checkArgument(isValidSchool(school), MESSAGE_SCHOOL_CONSTRAINTS); + this.school = school; + } + + /** + * Returns true if a given string is a valid school. + */ + public static boolean isValidSchool(String test) { + return test.matches(SCHOOL_VALIDATION_REGEX); + } + + + @Override + public String toString() { + return school; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof School // instanceof handles nulls + && this.school.equals(((School) other).school)); // state check + } + + @Override + public int hashCode() { + return school.hashCode(); + } +} diff --git a/src/main/java/seedu/address/model/tutee/SchoolContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/tutee/SchoolContainsKeywordsPredicate.java new file mode 100644 index 000000000000..a6e3e76e18e3 --- /dev/null +++ b/src/main/java/seedu/address/model/tutee/SchoolContainsKeywordsPredicate.java @@ -0,0 +1,36 @@ +package seedu.address.model.tutee; + +import java.util.List; +import java.util.function.Predicate; + +import seedu.address.commons.util.StringUtil; +import seedu.address.model.person.Person; + +/** + * Tests that a {@code Tutee}'s {@code School} matches any of the keywords given. + */ +public class SchoolContainsKeywordsPredicate implements Predicate { + private final List keywords; + + public SchoolContainsKeywordsPredicate(List keywords) { + this.keywords = keywords; + } + + @Override + public boolean test(Person person) { + if (!(person instanceof Tutee)) { + return false; + } else { + return keywords.stream() + .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(( + (Tutee) person).getSchool().toString(), keyword)); + } + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof SchoolContainsKeywordsPredicate // instanceof handles nulls + && this.keywords.equals(((SchoolContainsKeywordsPredicate) other).keywords)); // state check + } +} diff --git a/src/main/java/seedu/address/model/tutee/Subject.java b/src/main/java/seedu/address/model/tutee/Subject.java new file mode 100644 index 000000000000..f836b17b1414 --- /dev/null +++ b/src/main/java/seedu/address/model/tutee/Subject.java @@ -0,0 +1,54 @@ +package seedu.address.model.tutee; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +//@@author ChoChihTun +/** + * Represents a Tutee's subject + * Guarantees: immutable; is valid as declared in {@link #isValidSubject(String)} + */ +public class Subject { + + public static final String MESSAGE_SUBJECT_CONSTRAINTS = + "Subject should only contain alphabetic characters and spaces, and it should not be blank"; + public static final String SUBJECT_VALIDATION_REGEX = "[\\p{Alpha}][\\p{Alpha} ]*"; + + public final String subject; + + /** + * Constructs a {@code Subject}. + * + * @param subject A valid subject. + */ + public Subject(String subject) { + requireNonNull(subject); + checkArgument(isValidSubject(subject), MESSAGE_SUBJECT_CONSTRAINTS); + this.subject = subject; + } + + /** + * Returns true if a given string is a valid subject. + */ + public static boolean isValidSubject(String test) { + return test.matches(SUBJECT_VALIDATION_REGEX); + } + + + @Override + public String toString() { + return subject; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof Subject // instanceof handles nulls + && this.subject.equals(((Subject) other).subject)); // state check + } + + @Override + public int hashCode() { + return subject.hashCode(); + } +} diff --git a/src/main/java/seedu/address/model/tutee/SubjectContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/tutee/SubjectContainsKeywordsPredicate.java new file mode 100644 index 000000000000..eadb370d7cf8 --- /dev/null +++ b/src/main/java/seedu/address/model/tutee/SubjectContainsKeywordsPredicate.java @@ -0,0 +1,36 @@ +package seedu.address.model.tutee; + +import java.util.List; +import java.util.function.Predicate; + +import seedu.address.commons.util.StringUtil; +import seedu.address.model.person.Person; + +/** + * Tests that a {@code Tutee}'s {@code Subject} matches any of the keywords given. + */ +public class SubjectContainsKeywordsPredicate implements Predicate { + private final List keywords; + + public SubjectContainsKeywordsPredicate(List keywords) { + this.keywords = keywords; + } + + @Override + public boolean test(Person person) { + if (!(person instanceof Tutee)) { + return false; + } else { + return keywords.stream() + .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(( + (Tutee) person).getSubject().toString(), keyword)); + } + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof SubjectContainsKeywordsPredicate // instanceof handles nulls + && this.keywords.equals(((SubjectContainsKeywordsPredicate) other).keywords)); // state check + } +} diff --git a/src/main/java/seedu/address/model/tutee/TuitionTask.java b/src/main/java/seedu/address/model/tutee/TuitionTask.java new file mode 100644 index 000000000000..00a6a9ce5f5a --- /dev/null +++ b/src/main/java/seedu/address/model/tutee/TuitionTask.java @@ -0,0 +1,149 @@ +package seedu.address.model.tutee; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.time.format.ResolverStyle; + +import com.calendarfx.model.Entry; +import com.calendarfx.model.Interval; + +import seedu.address.model.Task; + +//@@author ChoChihTun +/** + * Represents a tuition task that the tutee has + */ +public class TuitionTask implements Task { + + private static final String TUITION_TITLE = "Tuition with %1$s"; + private static final String HOUR_DELIMITER = "h"; + private static final String MINUTE_DELIMITER = "m"; + private static final String NULL_STRING = ""; + private DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd/MM/uuuu HH:mm") + .withResolverStyle(ResolverStyle.STRICT); + + private String tutee; + private String description; + private String duration; + private LocalDateTime taskDateTime; + private Entry entry; + + /** + * Creates a tuition task + * + * @param tutee tutee involved in the task + * @param taskDateTime date and time of the task + * @param duration duration of the task + * @param description description of the task + */ + public TuitionTask(String tutee, LocalDateTime taskDateTime, String duration, String description) { + this.tutee = tutee; + this.taskDateTime = taskDateTime; + this.duration = duration; + this.description = description; + this.entry = createCalendarEntry(); + } + + /** + * Creates an entry to be entered into the calendar + * + * @return Calendar entry + */ + private Entry createCalendarEntry() { + LocalDateTime endDateTime = getTaskEndTime(); + Interval interval = new Interval(taskDateTime, endDateTime); + Entry entry = new Entry(getTuitionTitle()); + entry.setInterval(interval); + return entry; + } + + /** + * Returns the end time of the task + */ + private LocalDateTime getTaskEndTime() { + int hoursInDuration = parseHours(); + int minutesInDuration = parseMinutes(); + LocalDateTime endDateTime = taskDateTime.plusHours(hoursInDuration).plusMinutes(minutesInDuration); + return endDateTime; + } + + /** + * Parses hour component out of duration + * + * @return number of hours in the duration + */ + private int parseHours() { + int indexOfHourDelimiter = duration.indexOf(HOUR_DELIMITER); + return Integer.parseInt(duration.substring(0, indexOfHourDelimiter)); + } + + /** + * Parses minute component out of duration + * + * @return number of minutes in the duration + */ + private int parseMinutes() { + int indexOfFirstMinuteDigit = duration.indexOf(HOUR_DELIMITER) + 1; + int indexOfMinuteDelimiter = duration.indexOf(MINUTE_DELIMITER); + return Integer.parseInt(duration.substring(indexOfFirstMinuteDigit, indexOfMinuteDelimiter)); + } + + public Entry getEntry() { + return entry; + } + + public LocalDateTime getTaskDateTime() { + return taskDateTime; + } + + public String getPerson() { + return tutee; + } + + public String getDescription() { + return description; + } + + public String getDuration() { + return duration; + } + + @Override + public String getStringTaskDateTime() { + return taskDateTime.format(formatter); + } + + public String getTuitionTitle() { + return String.format(TUITION_TITLE, tutee); + } + + //@@author yungyung04 + @Override + public String toString() { + if (hasDescription()) { + return "Tuition task with description " + description + " on " + + Integer.toString(taskDateTime.getDayOfMonth()) + " " + taskDateTime.getMonth().name() + + " " + Integer.toString(taskDateTime.getYear()); + } else { + return "Tuition task without description on " + Integer.toString(taskDateTime.getDayOfMonth()) + + " " + taskDateTime.getMonth().name() + " " + Integer.toString(taskDateTime.getYear()); + } + } + + /** + * Returns true if the tuition task contains a non-empty description. + */ + private boolean hasDescription() { + return !description.equals(NULL_STRING); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof TuitionTask // instanceof handles nulls + && tutee.equals(((TuitionTask) other).tutee) + && taskDateTime.equals(((TuitionTask) other).taskDateTime) + && duration.equals(((TuitionTask) other).duration) + && description.equals(((TuitionTask) other).description)); + } +} diff --git a/src/main/java/seedu/address/model/tutee/Tutee.java b/src/main/java/seedu/address/model/tutee/Tutee.java new file mode 100644 index 000000000000..f0e1b6d32cfc --- /dev/null +++ b/src/main/java/seedu/address/model/tutee/Tutee.java @@ -0,0 +1,116 @@ +package seedu.address.model.tutee; + +import java.util.Objects; +import java.util.Set; + +import seedu.address.model.person.Address; +import seedu.address.model.person.Email; +import seedu.address.model.person.Name; +import seedu.address.model.person.Person; +import seedu.address.model.person.Phone; +import seedu.address.model.tag.Tag; +import seedu.address.model.tag.UniqueTagList; + +//@@author ChoChihTun +/** + * Represents a tutee in the address book. + * Guarantees: details are present and not null, field values are validated, immutable. + */ +public class Tutee extends Person { + private static final String TUTEE_TAG_NAME = "Tutee"; + + private Subject subject; + private Grade grade; + private EducationLevel educationLevel; + private School school; + + /** + * Every field must be present and not null. + */ + public Tutee(Name name, Phone phone, Email email, Address address, Subject subject, + Grade grade, EducationLevel educationLevel, School school, Set tags) { + super(name, phone, email, address, tags); + this.subject = subject; + this.grade = grade; + this.educationLevel = educationLevel; + this.school = school; + + // Creates a "Tutee" tag to represent a tutee + Tag tuteeTag = new Tag(TUTEE_TAG_NAME); + if (!this.tags.contains(tuteeTag)) { + try { + this.tags.add(tuteeTag); + } catch (UniqueTagList.DuplicateTagException e) { + // Should not have duplicate tutee tag + assert (false); + } + } + } + + public Subject getSubject() { + return subject; + } + + public Grade getGrade() { + return grade; + } + + public EducationLevel getEducationLevel() { + return educationLevel; + } + + public School getSchool() { + return school; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof Tutee)) { + return false; + } + + Tutee otherPerson = (Tutee) other; + return otherPerson.getName().equals(this.getName()) + && otherPerson.getPhone().equals(this.getPhone()) + && otherPerson.getEmail().equals(this.getEmail()) + && otherPerson.getAddress().equals(this.getAddress()) + && otherPerson.getEducationLevel().equals(this.getEducationLevel()) + && otherPerson.getGrade().equals(this.getGrade()) + && otherPerson.getSchool().equals(this.getSchool()) + && otherPerson.getSubject().equals(this.getSubject()); + } + + @Override + public int hashCode() { + // use this method for custom fields hashing instead of implementing your own + return Objects.hash(name, phone, email, address, subject, grade, educationLevel, school, tags); + } + + @Override + public String toString() { + final StringBuilder builder = new StringBuilder(); + builder.append(getName()) + .append(" Phone: ") + .append(getPhone()) + .append(" Email: ") + .append(getEmail()) + .append(" Address: ") + .append(getAddress()) + .append(" Subject: ") + .append(getSubject()) + .append(" Grade ") + .append(getGrade()) + .append(" Education Level: ") + .append(getEducationLevel()) + .append(" School: ") + .append(getSchool()) + .append(" Tags: "); + getTags().forEach(builder::append); + return builder.toString(); + } + +} diff --git a/src/main/java/seedu/address/model/util/SampleDataUtil.java b/src/main/java/seedu/address/model/util/SampleDataUtil.java index aea96bfb31f3..c33e0091bbef 100644 --- a/src/main/java/seedu/address/model/util/SampleDataUtil.java +++ b/src/main/java/seedu/address/model/util/SampleDataUtil.java @@ -1,22 +1,49 @@ package seedu.address.model.util; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.time.format.ResolverStyle; import java.util.HashSet; import java.util.Set; import seedu.address.model.AddressBook; import seedu.address.model.ReadOnlyAddressBook; +import seedu.address.model.Task; import seedu.address.model.person.Address; import seedu.address.model.person.Email; import seedu.address.model.person.Name; import seedu.address.model.person.Person; import seedu.address.model.person.Phone; import seedu.address.model.person.exceptions.DuplicatePersonException; +import seedu.address.model.personal.PersonalTask; import seedu.address.model.tag.Tag; +import seedu.address.model.task.exceptions.TimingClashException; +import seedu.address.model.tutee.EducationLevel; +import seedu.address.model.tutee.Grade; +import seedu.address.model.tutee.School; +import seedu.address.model.tutee.Subject; +import seedu.address.model.tutee.TuitionTask; +import seedu.address.model.tutee.Tutee; +//@@author a-shakra /** * Contains utility methods for populating {@code AddressBook} with sample data. */ public class SampleDataUtil { + public static final String DATETIME1 = "16/04/2018 15:15"; + public static final String DATETIME2 = "19/04/2018 09:25"; + public static final String DATETIME3 = "07/06/2018 16:45"; + public static final String DATETIME4 = "03/06/2019 12:10"; + public static final String DATETIME5 = "05/07/2020 18:45"; + public static final String DATETIME6 = "15/07/2018 06:55"; + public static final String DATETIME7 = "20/10/2018 11:11"; + public static final String DATETIME8 = "16/12/2018 08:18"; + public static final String DATETIME9 = "23/12/2018 10:28"; + public static final String DATETIME10 = "13/01/2018 11:30"; + public static final String DATETIME11 = "29/01/2018 12:30"; + private static DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd/MM/uuuu HH:mm") + .withResolverStyle(ResolverStyle.STRICT); + public static Person[] getSamplePersons() { return new Person[] { new Person(new Name("Alex Yeoh"), new Phone("87438807"), new Email("alexyeoh@example.com"), @@ -28,15 +55,36 @@ public static Person[] getSamplePersons() { 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")), - 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")), - 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")), - 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")) + new Tutee(new Name("Anas Shakra"), new Phone("514552256"), new Email("shakra.a@hotmail.com"), + new Address("590 Souart"), new Subject("Chemistry"), new Grade("A"), + new EducationLevel("secondary"), new School("NUS"), getTagSet("family")), + new Tutee(new Name("Irfan Ibrahim"), new Phone("92492021"), new Email("irfan@example.co"), + new Address("Blk 47 Tampines Street 20"), new Subject("Chemistry"), new Grade("C"), + new EducationLevel("secondary"), new School("NUS"), getTagSet("family")), + new Tutee(new Name("Roy Balakrishnan"), new Phone("92624417"), new Email("royb@example.com"), + new Address("Blk 45 Aljunied Street"), new Subject("Physics"), new Grade("A"), + new EducationLevel("secondary"), new School("NUS"), getTagSet("family")), + new Tutee(new Name("David Li"), new Phone("91031282"), new Email("lidavid@example.com"), + new Address("Blk 436 Serangoon Gardens Street"), new Subject("Math"), new Grade("C"), + new EducationLevel("primary"), new School("NUS"), getTagSet("friend")), + }; + } + public static Task[] getSampleTasks() { + return new Task[]{ + new PersonalTask(LocalDateTime.parse(DATETIME1, formatter), "2h15m", "exampleTask1"), + new TuitionTask("Anas Shakra", LocalDateTime.parse(DATETIME2, formatter), "5h25m", "exampleTask2"), + new PersonalTask(LocalDateTime.parse(DATETIME3, formatter), "3h45m", "exampleTask3"), + new PersonalTask(LocalDateTime.parse(DATETIME4, formatter), "12h10m", "exampleTask4"), + new PersonalTask(LocalDateTime.parse(DATETIME5, formatter), "02h45m", "exampleTask5"), + new PersonalTask(LocalDateTime.parse(DATETIME6, formatter), "06h55m", "exampleTask6"), + new PersonalTask(LocalDateTime.parse(DATETIME7, formatter), "03h11m", "exampleTask7"), + new PersonalTask(LocalDateTime.parse(DATETIME8, formatter), "08h18m", "exampleTask8"), + new TuitionTask("Roy Balakrishnan", LocalDateTime.parse(DATETIME9, formatter), "10h28m", + "exampleTask9"), + new TuitionTask("Irfan Ibrahim", LocalDateTime.parse(DATETIME10, formatter), "11h30m", + "exampleTask10"), + new TuitionTask("David Li", LocalDateTime.parse(DATETIME11, formatter), "12h30m", + "exampleTask11"), }; } @@ -46,11 +94,17 @@ public static ReadOnlyAddressBook getSampleAddressBook() { for (Person samplePerson : getSamplePersons()) { sampleAb.addPerson(samplePerson); } + for (Task sampleTask : getSampleTasks()) { + sampleAb.addTask(sampleTask); + } return sampleAb; } catch (DuplicatePersonException e) { throw new AssertionError("sample data cannot contain duplicate persons", e); + } catch (TimingClashException tce) { + throw new AssertionError("sample data cannot contain duplicate Tasks, tce"); } } + //@@author /** * Returns a tag set containing the list of strings given. diff --git a/src/main/java/seedu/address/storage/AddressBookStorage.java b/src/main/java/seedu/address/storage/AddressBookStorage.java index cf5b527c063a..9d851c3346ea 100644 --- a/src/main/java/seedu/address/storage/AddressBookStorage.java +++ b/src/main/java/seedu/address/storage/AddressBookStorage.java @@ -41,4 +41,11 @@ public interface AddressBookStorage { */ void saveAddressBook(ReadOnlyAddressBook addressBook, String filePath) throws IOException; + /** + * backs up (@link ReadOnlyAddressBook) to a local offline backup file + * + * @param addressBook cannot be null + * @throws IOException if there was any problem writing to the file + */ + void backupAddressBook(ReadOnlyAddressBook addressBook) throws IOException; } diff --git a/src/main/java/seedu/address/storage/StorageManager.java b/src/main/java/seedu/address/storage/StorageManager.java index 53967b391a5a..e13f18ff44b7 100644 --- a/src/main/java/seedu/address/storage/StorageManager.java +++ b/src/main/java/seedu/address/storage/StorageManager.java @@ -77,6 +77,12 @@ public void saveAddressBook(ReadOnlyAddressBook addressBook, String filePath) th addressBookStorage.saveAddressBook(addressBook, filePath); } + @Override + public void backupAddressBook(ReadOnlyAddressBook addressBook) throws IOException { + logger.fine("Attempting to write to an offline backup data file: "); + addressBookStorage.backupAddressBook(addressBook); + } + @Override @Subscribe diff --git a/src/main/java/seedu/address/storage/XmlAdaptedPerson.java b/src/main/java/seedu/address/storage/XmlAdaptedPerson.java index 2cd92dc4fd20..0375c0a4f6ba 100644 --- a/src/main/java/seedu/address/storage/XmlAdaptedPerson.java +++ b/src/main/java/seedu/address/storage/XmlAdaptedPerson.java @@ -15,6 +15,11 @@ import seedu.address.model.person.Person; import seedu.address.model.person.Phone; import seedu.address.model.tag.Tag; +import seedu.address.model.tutee.EducationLevel; +import seedu.address.model.tutee.Grade; +import seedu.address.model.tutee.School; +import seedu.address.model.tutee.Subject; +import seedu.address.model.tutee.Tutee; /** * JAXB-friendly version of the Person. @@ -31,6 +36,14 @@ public class XmlAdaptedPerson { private String email; @XmlElement(required = true) private String address; + @XmlElement(required = true) + private String subject; + @XmlElement(required = true) + private String grade; + @XmlElement(required = true) + private String educationLevel; + @XmlElement(required = true) + private String school; @XmlElement private List tagged = new ArrayList<>(); @@ -42,13 +55,25 @@ public class XmlAdaptedPerson { public XmlAdaptedPerson() {} /** - * Constructs an {@code XmlAdaptedPerson} with the given person details. + * Constructs an {@code XmlAdaptedPerson} with given person details. */ public XmlAdaptedPerson(String name, String phone, String email, String address, List tagged) { + this(name, phone, email, address, null, null, null, null, tagged); + } + + /** + * Constructs an {@code XmlAdaptedPerson} with the given tutee details. + */ + public XmlAdaptedPerson(String name, String phone, String email, String address, String subject, String grade, + String educationLevel, String school, List tagged) { this.name = name; this.phone = phone; this.email = email; this.address = address; + this.subject = subject; + this.grade = grade; + this.educationLevel = educationLevel; + this.school = school; if (tagged != null) { this.tagged = new ArrayList<>(tagged); } @@ -68,6 +93,12 @@ public XmlAdaptedPerson(Person source) { for (Tag tag : source.getTags()) { tagged.add(new XmlAdaptedTag(tag)); } + if (source instanceof Tutee) { + subject = ((Tutee) source).getSubject().subject; + grade = ((Tutee) source).getGrade().grade; + educationLevel = ((Tutee) source).getEducationLevel().educationLevel; + school = ((Tutee) source).getSchool().school; + } } /** @@ -114,7 +145,53 @@ public Person toModelType() throws IllegalValueException { final Address address = new Address(this.address); final Set tags = new HashSet<>(personTags); - return new Person(name, phone, email, address, tags); + + //@@author yungyung04 + if (isTutee(personTags)) { + if (this.subject == null) { + throw new IllegalValueException( + String.format(MISSING_FIELD_MESSAGE_FORMAT, Subject.class.getSimpleName())); + } + if (!Subject.isValidSubject(this.subject)) { + throw new IllegalValueException(Subject.MESSAGE_SUBJECT_CONSTRAINTS); + } + final Subject subject = new Subject(this.subject); + + if (this.grade == null) { + throw new IllegalValueException( + String.format(MISSING_FIELD_MESSAGE_FORMAT, Grade.class.getSimpleName())); + } + if (!Grade.isValidGrade(this.grade)) { + throw new IllegalValueException(Grade.MESSAGE_GRADE_CONSTRAINTS); + } + final Grade grade = new Grade(this.grade); + + if (this.educationLevel == null) { + throw new IllegalValueException( + String.format(MISSING_FIELD_MESSAGE_FORMAT, EducationLevel.class.getSimpleName())); + } + if (!EducationLevel.isValidEducationLevel(this.educationLevel)) { + throw new IllegalValueException(EducationLevel.MESSAGE_EDUCATION_LEVEL_CONSTRAINTS); + } + final EducationLevel educationLevel = new EducationLevel(this.educationLevel); + + if (this.school == null) { + throw new IllegalValueException( + String.format(MISSING_FIELD_MESSAGE_FORMAT, School.class.getSimpleName())); + } + if (!School.isValidSchool(this.school)) { + throw new IllegalValueException(School.MESSAGE_SCHOOL_CONSTRAINTS); + } + final School school = new School(this.school); + + return new Tutee(name, phone, email, address, subject, grade, educationLevel, school, tags); + } else { + return new Person(name, phone, email, address, tags); + } + } + //@@author + private boolean isTutee(List personTags) { + return personTags.contains(new Tag("Tutee")); } @Override diff --git a/src/main/java/seedu/address/storage/XmlAdaptedTask.java b/src/main/java/seedu/address/storage/XmlAdaptedTask.java new file mode 100644 index 000000000000..8bfc83940bc0 --- /dev/null +++ b/src/main/java/seedu/address/storage/XmlAdaptedTask.java @@ -0,0 +1,115 @@ +package seedu.address.storage; + +import java.time.LocalDateTime; + +import java.util.Objects; + +import javax.xml.bind.annotation.XmlElement; + +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.model.Task; +import seedu.address.model.personal.PersonalTask; +import seedu.address.model.tutee.TuitionTask; + +/** + * JAXB-friendly version of the Task. + */ +//@@author a-shakra +public class XmlAdaptedTask { + + public static final String MISSING_FIELD_MESSAGE_FORMAT = "Task's %s field is missing!"; + + @XmlElement(required = true) + private String name; + @XmlElement(required = true) + private String description; + @XmlElement(required = true) + private String duration; + @XmlElement(required = true) + private String dateAndTime; + + /** + * Constructs an XmlAdaptedTask. + * This is the no-arg constructor that is required by JAXB. + */ + public XmlAdaptedTask() {} + + /** + * Constructs an {@code XmlAdaptedTask} with given personal task details. + */ + public XmlAdaptedTask(String description, String duration, String dateAndTime) { + //this.name = "null"; + this.description = description; + this.duration = duration; + this.dateAndTime = dateAndTime; + } + + public XmlAdaptedTask(String name, String description, String duration, String dateAndTime) { + this.name = name; + this.description = description; + this.duration = duration; + this.dateAndTime = dateAndTime; + } + + /** + * Converts a given Task into this class for JAXB use. + * + */ + public XmlAdaptedTask(Task source) { + description = source.getDescription(); + duration = source.getDuration(); + dateAndTime = source.getTaskDateTime().toString(); + if (source instanceof TuitionTask) { + name = ((TuitionTask) source).getPerson(); + } + } + + /** + * Converts this jaxb-friendly adapted task object into the model's Task object. + * + * @throws IllegalValueException if there were any data constraints violated in the adapted person + * Because of the way Task was designed (As an interface), i'm forced to just input this as a PersonalTask + * until a better solution can be found + */ + + public Task toModelType() throws IllegalValueException { + LocalDateTime taskDateTime = LocalDateTime.parse(dateAndTime); + if (this.description == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, + Task.MESSAGE_DESCRIPTION_CONSTRAINTS)); + } + if (this.duration == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, + Task.MESSAGE_DURATION_CONSTRAINTS)); + } + if (this.dateAndTime == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, + Task.MESSAGE_DATETIME_CONSTRAINTS)); + } + if (this.name == null) { + return new PersonalTask(taskDateTime, duration, description); + } else { + return new TuitionTask(name, taskDateTime, duration, description); + } + } + + /** + * Returns true if the two tasks are equal. Needs to be updated to reflect the name parameter + */ + public boolean equals(Object other) { + + if (other == this) { + return true; + } + + if (!(other instanceof XmlAdaptedTask)) { + return false; + } + + XmlAdaptedTask otherTask = (XmlAdaptedTask) other; + return Objects.equals(description, otherTask.description) + && Objects.equals(duration, otherTask.duration) + && Objects.equals(dateAndTime, otherTask.dateAndTime) + && Objects.equals(name, otherTask.name); + } +} diff --git a/src/main/java/seedu/address/storage/XmlAddressBookStorage.java b/src/main/java/seedu/address/storage/XmlAddressBookStorage.java index c77ebe67435c..e3f39a953418 100644 --- a/src/main/java/seedu/address/storage/XmlAddressBookStorage.java +++ b/src/main/java/seedu/address/storage/XmlAddressBookStorage.java @@ -3,7 +3,6 @@ import static java.util.Objects.requireNonNull; import java.io.File; -import java.io.FileNotFoundException; import java.io.IOException; import java.util.Optional; import java.util.logging.Logger; @@ -11,6 +10,7 @@ import seedu.address.commons.core.LogsCenter; import seedu.address.commons.exceptions.DataConversionException; import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.commons.util.EncryptionUtil; import seedu.address.commons.util.FileUtil; import seedu.address.model.ReadOnlyAddressBook; @@ -42,7 +42,7 @@ public Optional readAddressBook() throws DataConversionExce * @throws DataConversionException if the file is not in the correct format. */ public Optional readAddressBook(String filePath) throws DataConversionException, - FileNotFoundException { + IOException { requireNonNull(filePath); File addressBookFile = new File(filePath); @@ -51,8 +51,10 @@ public Optional readAddressBook(String filePath) throws Dat logger.info("AddressBook file " + addressBookFile + " not found"); return Optional.empty(); } - + EncryptionUtil.decrypt(addressBookFile); XmlSerializableAddressBook xmlAddressBook = XmlFileStorage.loadDataFromSaveFile(new File(filePath)); + EncryptionUtil.encrypt(addressBookFile); + try { return Optional.of(xmlAddressBook.toModelType()); } catch (IllegalValueException ive) { @@ -76,7 +78,18 @@ public void saveAddressBook(ReadOnlyAddressBook addressBook, String filePath) th File file = new File(filePath); FileUtil.createIfMissing(file); + EncryptionUtil.decrypt(file); XmlFileStorage.saveDataToFile(file, new XmlSerializableAddressBook(addressBook)); + EncryptionUtil.encrypt(file); + } + + /** + * Similar to {@link #saveAddressBook(ReadOnlyAddressBook)} + * @param addressBook of the data. Cannot be null + */ + public void backupAddressBook(ReadOnlyAddressBook addressBook) throws IOException { + requireNonNull(addressBook); + saveAddressBook(addressBook, filePath + ".backup"); } } diff --git a/src/main/java/seedu/address/storage/XmlFileStorage.java b/src/main/java/seedu/address/storage/XmlFileStorage.java index 289fcb63038e..73cf719f0532 100644 --- a/src/main/java/seedu/address/storage/XmlFileStorage.java +++ b/src/main/java/seedu/address/storage/XmlFileStorage.java @@ -1,7 +1,7 @@ package seedu.address.storage; import java.io.File; -import java.io.FileNotFoundException; +import java.io.IOException; import javax.xml.bind.JAXBException; @@ -16,7 +16,7 @@ public class XmlFileStorage { * Saves the given addressbook data to the specified file. */ public static void saveDataToFile(File file, XmlSerializableAddressBook addressBook) - throws FileNotFoundException { + throws IOException { try { XmlUtil.saveDataToFile(file, addressBook); } catch (JAXBException e) { @@ -28,8 +28,9 @@ public static void saveDataToFile(File file, XmlSerializableAddressBook addressB * Returns address book in the file or an empty address book */ public static XmlSerializableAddressBook loadDataFromSaveFile(File file) throws DataConversionException, - FileNotFoundException { + IOException { try { + //EncryptionUtil.decrypt(file); return XmlUtil.getDataFromFile(file, XmlSerializableAddressBook.class); } catch (JAXBException e) { throw new DataConversionException(e); diff --git a/src/main/java/seedu/address/storage/XmlSerializableAddressBook.java b/src/main/java/seedu/address/storage/XmlSerializableAddressBook.java index dc820896c312..4ba6164608c2 100644 --- a/src/main/java/seedu/address/storage/XmlSerializableAddressBook.java +++ b/src/main/java/seedu/address/storage/XmlSerializableAddressBook.java @@ -14,6 +14,7 @@ /** * An Immutable AddressBook that is serializable to XML format */ +//@@author a-shakra @XmlRootElement(name = "addressbook") public class XmlSerializableAddressBook { @@ -21,6 +22,8 @@ public class XmlSerializableAddressBook { private List persons; @XmlElement private List tags; + @XmlElement + private List tasks; /** * Creates an empty XmlSerializableAddressBook. @@ -29,6 +32,7 @@ public class XmlSerializableAddressBook { public XmlSerializableAddressBook() { persons = new ArrayList<>(); tags = new ArrayList<>(); + tasks = new ArrayList<>(); } /** @@ -38,6 +42,7 @@ public XmlSerializableAddressBook(ReadOnlyAddressBook src) { this(); persons.addAll(src.getPersonList().stream().map(XmlAdaptedPerson::new).collect(Collectors.toList())); tags.addAll(src.getTagList().stream().map(XmlAdaptedTag::new).collect(Collectors.toList())); + tasks.addAll(src.getTaskList().stream().map(XmlAdaptedTask::new).collect(Collectors.toList())); } /** @@ -54,6 +59,9 @@ public AddressBook toModelType() throws IllegalValueException { for (XmlAdaptedPerson p : persons) { addressBook.addPerson(p.toModelType()); } + for (XmlAdaptedTask t: tasks) { + addressBook.addTask(t.toModelType()); + } return addressBook; } @@ -68,6 +76,7 @@ public boolean equals(Object other) { } XmlSerializableAddressBook otherAb = (XmlSerializableAddressBook) other; - return persons.equals(otherAb.persons) && tags.equals(otherAb.tags); + // The tasks condition might be a problem because of the design of tasks + return persons.equals(otherAb.persons) && tags.equals(otherAb.tags) && tasks.equals(otherAb.tasks); } } diff --git a/src/main/java/seedu/address/ui/BrowserPanel.java b/src/main/java/seedu/address/ui/BrowserPanel.java deleted file mode 100644 index bb0d61380d5a..000000000000 --- a/src/main/java/seedu/address/ui/BrowserPanel.java +++ /dev/null @@ -1,72 +0,0 @@ -package seedu.address.ui; - -import java.net.URL; -import java.util.logging.Logger; - -import com.google.common.eventbus.Subscribe; - -import javafx.application.Platform; -import javafx.event.Event; -import javafx.fxml.FXML; -import javafx.scene.layout.Region; -import javafx.scene.web.WebView; -import seedu.address.MainApp; -import seedu.address.commons.core.LogsCenter; -import seedu.address.commons.events.ui.PersonPanelSelectionChangedEvent; -import seedu.address.model.person.Person; - -/** - * The Browser Panel of the App. - */ -public class BrowserPanel extends UiPart { - - public static final String DEFAULT_PAGE = "default.html"; - public static final String SEARCH_PAGE_URL = - "https://se-edu.github.io/addressbook-level4/DummySearchPage.html?name="; - - private static final String FXML = "BrowserPanel.fxml"; - - private final Logger logger = LogsCenter.getLogger(this.getClass()); - - @FXML - private WebView browser; - - public BrowserPanel() { - super(FXML); - - // To prevent triggering events for typing inside the loaded Web page. - getRoot().setOnKeyPressed(Event::consume); - - loadDefaultPage(); - registerAsAnEventHandler(this); - } - - private void loadPersonPage(Person person) { - loadPage(SEARCH_PAGE_URL + person.getName().fullName); - } - - public void loadPage(String url) { - Platform.runLater(() -> browser.getEngine().load(url)); - } - - /** - * Loads a default HTML file with a background that matches the general theme. - */ - private void loadDefaultPage() { - URL defaultPage = MainApp.class.getResource(FXML_FILE_FOLDER + DEFAULT_PAGE); - loadPage(defaultPage.toExternalForm()); - } - - /** - * Frees resources allocated to the browser. - */ - public void freeResources() { - browser = null; - } - - @Subscribe - private void handlePersonPanelSelectionChangedEvent(PersonPanelSelectionChangedEvent event) { - logger.info(LogsCenter.getEventHandlingLogMessage(event)); - loadPersonPage(event.getNewSelection().person); - } -} diff --git a/src/main/java/seedu/address/ui/CalendarPanel.java b/src/main/java/seedu/address/ui/CalendarPanel.java new file mode 100644 index 000000000000..1139219c8b40 --- /dev/null +++ b/src/main/java/seedu/address/ui/CalendarPanel.java @@ -0,0 +1,149 @@ +package seedu.address.ui; + +import java.time.LocalDate; +import java.time.LocalTime; +import java.util.List; + +import com.calendarfx.model.Calendar; +import com.calendarfx.model.CalendarSource; +import com.calendarfx.model.Entry; +import com.calendarfx.view.CalendarView; + +import javafx.fxml.FXML; +import javafx.scene.layout.Region; +import seedu.address.model.Task; + +//@@author ChoChihTun +/** + * The Calendar Panel of the App. + */ +public class CalendarPanel extends UiPart { + + private static final String FXML = "CalendarPanel.fxml"; + private static final char DAY = 'd'; + private static final char WEEK = 'w'; + private static final char MONTH = 'm'; + private static final char YEAR = 'y'; + private static CalendarSource source = new CalendarSource("Schedule"); + private static Calendar calendar = new Calendar("Task"); + + @FXML + private static CalendarView calendarView = new CalendarView(); + + + public CalendarPanel() { + super(FXML); + calendarView.setRequestedTime(LocalTime.now()); + calendarView.setToday(LocalDate.now()); + calendarView.setTime(LocalTime.now()); + calendarView.setScaleX(0.95); + calendarView.setScaleY(1.15); + calendarView.setTranslateY(-40); + calendarView.showDayPage(); + disableViews(); + setupCalendar(); + } + + /** + * Initialises the calendar + */ + private void setupCalendar() { + source.getCalendars().add(calendar); + calendarView.getCalendarSources().add(source); + } + + /** + * Removes unnecessary buttons from interface + */ + private void disableViews() { + calendarView.setShowAddCalendarButton(false); + calendarView.setShowPrintButton(false); + calendarView.setShowPageToolBarControls(false); + calendarView.setShowSearchField(false); + } + + /** + * Changes the view page of the calendar + * @param timeUnit the view page time unit to be changed into + */ + public static void changeViewPage(char timeUnit) { + switch(timeUnit) { + case DAY: + calendarView.showDayPage(); + return; + case WEEK: + calendarView.showWeekPage(); + return; + case MONTH: + calendarView.showMonthPage(); + return; + case YEAR: + calendarView.showYearPage(); + return; + default: + // Should never enter here + assert (false); + } + } + + /** + * Updates the calendar with the updated list of tasks + * + * @param filteredTasks updated list of tasks + */ + public static void updateCalendar(List filteredTasks) { + if (isFilteredTaskListValid(filteredTasks)) { + Calendar updatedCalendar = new Calendar("task"); + for (Task task : filteredTasks) { + updatedCalendar.addEntry(task.getEntry()); + } + source.getCalendars().clear(); + source.getCalendars().add(updatedCalendar); + } else { + // Latest task list provided or loaded from storage should not have any task that clashes + assert (false); + } + } + + /** + * Checks if the given latest task list is valid + * + * @param taskList to be checked + * @return true if there is no clash between tasks so task list is valid + * false if there is clash between tasks so task list is invalid + */ + private static boolean isFilteredTaskListValid(List taskList) { + for (int i = 0; i < taskList.size(); i++) { + Entry taskEntryToBeChecked = taskList.get(i).getEntry(); + if (isTaskTimingClash(taskList, i, taskEntryToBeChecked)) { + return false; + } + } + return true; + } + + /** + * Checks if the given task clashes with any task in the list + * + * @param taskList list of tasks to check against + * @param index index of the given task + * @param taskEntryToBeChecked the given task entry + * @return true if given task does not clash with any task in the list + * false if given task clashes with another task in the list + */ + private static boolean isTaskTimingClash(List taskList, int index, Entry taskEntryToBeChecked) { + for (int j = index + 1; j < taskList.size(); j++) { + Entry taskEntryToCheckAgainst = taskList.get(j).getEntry(); + if (taskEntryToBeChecked.intersects(taskEntryToCheckAgainst)) { + return true; + } + } + return false; + } + + @Override + public CalendarView getRoot() { + return calendarView; + } + +} diff --git a/src/main/java/seedu/address/ui/MainWindow.java b/src/main/java/seedu/address/ui/MainWindow.java index 20ad5fee906a..b0410dea8250 100644 --- a/src/main/java/seedu/address/ui/MainWindow.java +++ b/src/main/java/seedu/address/ui/MainWindow.java @@ -34,13 +34,14 @@ public class MainWindow extends UiPart { private Logic logic; // Independent Ui parts residing in this Ui container - private BrowserPanel browserPanel; private PersonListPanel personListPanel; + private TaskCardListPanel taskListPanel; private Config config; private UserPrefs prefs; + private CalendarPanel calendarPanel; @FXML - private StackPane browserPlaceholder; + private StackPane calendarPlaceholder; @FXML private StackPane commandBoxPlaceholder; @@ -51,6 +52,9 @@ public class MainWindow extends UiPart { @FXML private StackPane personListPanelPlaceholder; + @FXML + private StackPane taskListPanelPlaceholder; + @FXML private StackPane resultDisplayPlaceholder; @@ -62,11 +66,13 @@ public MainWindow(Stage primaryStage, Config config, UserPrefs prefs, Logic logi // Set dependencies this.primaryStage = primaryStage; + primaryStage.setMaximized(true); this.logic = logic; this.config = config; this.prefs = prefs; // Configure the UI + config.setAppTitle("TuitionConnect"); setTitle(config.getAppTitle()); setWindowDefaultSize(prefs); @@ -116,12 +122,15 @@ private void setAccelerator(MenuItem menuItem, KeyCombination keyCombination) { * Fills up all the placeholders of this window. */ void fillInnerParts() { - browserPanel = new BrowserPanel(); - browserPlaceholder.getChildren().add(browserPanel.getRoot()); + calendarPanel = new CalendarPanel(); + calendarPlaceholder.getChildren().add(calendarPanel.getRoot()); personListPanel = new PersonListPanel(logic.getFilteredPersonList()); personListPanelPlaceholder.getChildren().add(personListPanel.getRoot()); + taskListPanel = new TaskCardListPanel(logic.getFilteredTaskList()); + taskListPanelPlaceholder.getChildren().add(taskListPanel.getRoot()); + ResultDisplay resultDisplay = new ResultDisplay(); resultDisplayPlaceholder.getChildren().add(resultDisplay.getRoot()); @@ -185,8 +194,8 @@ public PersonListPanel getPersonListPanel() { return this.personListPanel; } - void releaseResources() { - browserPanel.freeResources(); + public TaskCardListPanel getTaskListPanel() { + return this.taskListPanel; } @Subscribe 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 000000000000..7e05b98a9a2d --- /dev/null +++ b/src/main/java/seedu/address/ui/TaskCard.java @@ -0,0 +1,74 @@ +package seedu.address.ui; + +import java.time.format.DateTimeFormatter; +import java.time.format.ResolverStyle; + +import javafx.fxml.FXML; +import javafx.scene.control.Label; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Region; +import seedu.address.model.Task; +import seedu.address.model.tutee.TuitionTask; + +/** + * An UI component that displays information of a {@code Task}. + */ +//@@author a-shakra +public class TaskCard extends UiPart { + private static final String FXML = "TaskListCard.fxml"; + public final Task task; + private DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd/MM/uuuu HH:mm") + .withResolverStyle(ResolverStyle.STRICT); + + /** + * Note: Certain keywords such as "location" and "resources" are reserved keywords in JavaFX. + * As a consequence, UI elements' variable names cannot be set to such keywords + * or an exception will be thrown by JavaFX during runtime. + * + * @see The issue on AddressBook level 4 + */ + + @FXML + private HBox cardPane; + @FXML + private Label id; + @FXML + private Label taskDateAndTime; + @FXML + private Label duration; + @FXML + private Label description; + + public TaskCard(Task task, int displayedIndex) { + super(FXML); + this.task = task; + id.setText(displayedIndex + ". "); + if (task instanceof TuitionTask) { + description.setText(((TuitionTask) task).getTuitionTitle()); + } else { + description.setText(task.getDescription()); + } + duration.setText(task.getDuration()); + taskDateAndTime.setText(task.getTaskDateTime().format(formatter)); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof TaskCard)) { + return false; + } + + // state check + TaskCard card = (TaskCard) other; + return id.getText().equals(card.id.getText()) + && task.equals(card.task); + } + +} + diff --git a/src/main/java/seedu/address/ui/TaskCardListPanel.java b/src/main/java/seedu/address/ui/TaskCardListPanel.java new file mode 100644 index 000000000000..5f684241ad62 --- /dev/null +++ b/src/main/java/seedu/address/ui/TaskCardListPanel.java @@ -0,0 +1,91 @@ +package seedu.address.ui; + +import java.util.logging.Logger; + +import org.fxmisc.easybind.EasyBind; + +import com.google.common.eventbus.Subscribe; + +import javafx.application.Platform; +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.commons.events.ui.JumpToListRequestEvent; +import seedu.address.commons.events.ui.TaskPanelSelectionChangedEvent; +import seedu.address.model.Task; + + +/** + * Panel containing the list of tasks. + */ +//@@author a-shakra +public class TaskCardListPanel extends UiPart { + private static final String FXML = "TaskCardListPanel.fxml"; + private final Logger logger = LogsCenter.getLogger(TaskCardListPanel.class); + + @FXML + private ListView taskListView; + + public TaskCardListPanel(ObservableList taskList) { + super(FXML); + setConnections(taskList); + registerAsAnEventHandler(this); + } + + private void setConnections(ObservableList taskList) { + ObservableList mappedList = EasyBind.map( + taskList, (task) -> new TaskCard(task, taskList.indexOf(task) + 1)); + taskListView.setItems(mappedList); + taskListView.setCellFactory(listView -> new TaskListViewCell()); + setEventHandlerForSelectionChangeEvent(); + } + + + private void setEventHandlerForSelectionChangeEvent() { + taskListView.getSelectionModel().selectedItemProperty() + .addListener((observable, oldValue, newValue) -> { + if (newValue != null) { + logger.fine("Selection in Task list panel changed to : '" + newValue + "'"); + raise(new TaskPanelSelectionChangedEvent(newValue)); + } + }); + } + + /** + * Scrolls to the {@code PersonCard} at the {@code index} and selects it. + */ + private void scrollTo(int index) { + Platform.runLater(() -> { + taskListView.scrollTo(index); + taskListView.getSelectionModel().clearAndSelect(index); + }); + } + + @Subscribe + private void handleJumpToListRequestEvent(JumpToListRequestEvent event) { + logger.info(LogsCenter.getEventHandlingLogMessage(event)); + scrollTo(event.targetIndex); + } + + /** + * Custom {@code ListCell} that displays the graphics of a {@code PersonCard}. + */ + class TaskListViewCell extends ListCell { + + @Override + protected void updateItem(TaskCard task, boolean empty) { + super.updateItem(task, empty); + + if (empty || task == null) { + setGraphic(null); + setText(null); + } else { + setGraphic(task.getRoot()); + } + } + } + +} diff --git a/src/main/java/seedu/address/ui/UiManager.java b/src/main/java/seedu/address/ui/UiManager.java index 3fd3c17be156..fa00b25e179b 100644 --- a/src/main/java/seedu/address/ui/UiManager.java +++ b/src/main/java/seedu/address/ui/UiManager.java @@ -66,7 +66,6 @@ public void start(Stage primaryStage) { public void stop() { prefs.updateLastUsedGuiSetting(mainWindow.getCurrentGuiSetting()); mainWindow.hide(); - mainWindow.releaseResources(); } private void showFileOperationAlertAndWait(String description, String details, Throwable cause) { diff --git a/src/main/resources/view/BrowserPanel.fxml b/src/main/resources/view/BrowserPanel.fxml deleted file mode 100644 index 31670827e3da..000000000000 --- a/src/main/resources/view/BrowserPanel.fxml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/src/main/resources/view/CalendarPanel.fxml b/src/main/resources/view/CalendarPanel.fxml new file mode 100644 index 000000000000..865ef214d96e --- /dev/null +++ b/src/main/resources/view/CalendarPanel.fxml @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/src/main/resources/view/Extensions.css b/src/main/resources/view/Extensions.css index bfe82a85964d..2b0f3e014b3d 100644 --- a/src/main/resources/view/Extensions.css +++ b/src/main/resources/view/Extensions.css @@ -1,6 +1,6 @@ .error { - -fx-text-fill: #d06651 !important; /* The error class should always override the default text-fill style */ + -fx-text-fill: red !important; /* The error class should always override the default text-fill style */ } .list-cell:empty { diff --git a/src/main/resources/view/MainWindow.fxml b/src/main/resources/view/MainWindow.fxml index 1dadb95b6ffe..af629fe137d9 100644 --- a/src/main/resources/view/MainWindow.fxml +++ b/src/main/resources/view/MainWindow.fxml @@ -11,6 +11,7 @@ + @@ -19,49 +20,64 @@ - + - - - - - - - - + + + + + + + + + + + + - - - - - + + + + + - - - - - + + + + + - - - - - - - + - - - - - - + + + + + + - + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/view/TaskCardListPanel.fxml b/src/main/resources/view/TaskCardListPanel.fxml new file mode 100644 index 000000000000..b3ceea697eac --- /dev/null +++ b/src/main/resources/view/TaskCardListPanel.fxml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/main/resources/view/TaskListCard.fxml b/src/main/resources/view/TaskListCard.fxml new file mode 100644 index 000000000000..47457d19f407 --- /dev/null +++ b/src/main/resources/view/TaskListCard.fxml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/view/TuitionConnectTheme.css b/src/main/resources/view/TuitionConnectTheme.css new file mode 100644 index 000000000000..c20d83940932 --- /dev/null +++ b/src/main/resources/view/TuitionConnectTheme.css @@ -0,0 +1,375 @@ +/* @@author ChoChihTun */ +.background { + -fx-background-color: white; + background-color: white; /* Used in the default.html file */ +} + +.label { + -fx-font-size: 10pt; + -fx-font-family: "Andale Mono"; + -fx-text-fill: black; + -fx-opacity: 0.9; +} + +.label-bright { + -fx-font-size: 10pt; + -fx-font-family: "Andale Mono"; + -fx-text-fill: black; + -fx-opacity: 1; +} + +.label-header { + -fx-font-size: 30pt; + -fx-font-family: "Andale Mono"; + -fx-text-fill: black; + -fx-opacity: 1; +} + +.text-field { + -fx-font-size: 12pt; + -fx-font-family: "Andale Mono"; +} + +.tab-pane { + -fx-padding: 0 0 0 1; +} + +.tab-pane .tab-header-area { + -fx-padding: 0 0 0 0; + -fx-min-height: 0; + -fx-max-height: 0; +} + +.table-view { + -fx-base: black; + -fx-control-inner-background: black; + -fx-background-color: black; + -fx-table-cell-border-color: transparent; + -fx-table-header-border-color: transparent; + -fx-padding: 5; +} + +.table-view .column-header-background { + -fx-background-color: transparent; +} + +.table-view .column-header, .table-view .filler { + -fx-size: 35; + -fx-border-width: 0 0 1 0; + -fx-background-color: transparent; + -fx-border-color: + transparent + transparent + derive(-fx-base, 80%) + transparent; + -fx-border-insets: 0 10 1 0; +} + +.table-view .column-header .label { + -fx-font-size: 20pt; + -fx-font-family: "Andale Mono"; + -fx-text-fill: white; + -fx-alignment: center-left; + -fx-opacity: 1; +} + +.table-view:focused .table-row-cell:filled:focused:selected { + -fx-background-color: -fx-focus-color; +} + +.split-pane:horizontal .split-pane-divider { + -fx-background-color: white; + -fx-border-color: transparent transparent transparent white; +} + +.split-pane { + -fx-background-color: white; +} + +.list-view { + -fx-background-insets: 0; + -fx-padding: 0; + -fx-background-color: derive(#1d1d1d, 20%); +} + +.list-cell { + -fx-label-padding: 0 0 0 0; + -fx-graphic-text-gap : 0; + -fx-padding: 0 0 0 0; +} + +.list-cell:filled:even { + -fx-background-color: #c9d8ef; +} + +.list-cell:filled:odd { + -fx-background-color: #c9d8ef; +} + +.list-cell:filled:selected { + -fx-background-color: #1f3351; +} + +.list-cell:filled:selected #cardPane { + -fx-border-color: black; + -fx-border-width: 3px; +} + +.list-cell .label { + -fx-text-fill: white; +} + +.cell_big_label { /* Name */ + -fx-font-family: "Segoe UI Semibold"; + -fx-font-size: 16px; + -fx-text-fill: white; +} + +.cell_small_label { /* Details */ + -fx-font-family: "Segoe UI"; + -fx-font-size: 13px; + -fx-text-fill: white; +} + +.anchor-pane { + -fx-background-color: #c9bbbb; +} + +.pane-with-border { + -fx-background-color: black; + -fx-border-color: transparent; + -fx-border-top-width: 1px; +} + +.status-bar { + -fx-background-color: white; + -fx-text-fill: black; +} + +.result-display { /* Command result */ + -fx-background-color: transparent; + -fx-font-family: "Andale Mono"; + -fx-font-size: 13pt; + -fx-text-fill: black; +} + +.result-display .label { + -fx-text-fill: black !important; +} + +.status-bar .label { + -fx-font-family: "Segoe UI Light"; + -fx-text-fill: white; +} + +.status-bar-with-border { + -fx-background-color: derive(#1d1d1d, 30%); + -fx-border-color: derive(#1d1d1d, 25%); + -fx-border-width: 1px; +} + +.status-bar-with-border .label { + -fx-text-fill: white; +} + +.grid-pane { + -fx-background-color: white; + -fx-border-color: black; + -fx-border-width: 1px; +} + +.grid-pane .anchor-pane { + -fx-background-color: #113756; +} + +.context-menu { + -fx-background-color: #22529e; +} + +.context-menu .label { + -fx-text-fill: white; +} + +.menu-bar { + -fx-background-color: #113756; +} + +.menu-bar .label { + -fx-font-size: 14pt; + -fx-font-family: "Andale Mono"; + -fx-text-fill: white; + -fx-opacity: 0.9; +} + +.menu .left-container { + -fx-background-color: black; +} + +/* + * Metro style Push Button + * Author: Pedro Duque Vieira + * http://pixelduke.wordpress.com/2012/10/23/jmetro-windows-8-controls-on-java/ + */ +.button { + -fx-padding: 5 22 5 22; + -fx-border-color: #e2e2e2; + -fx-border-width: 2; + -fx-background-radius: 0; + -fx-background-color: #1d1d1d; + -fx-font-family: "Andale Mono", Helvetica, Arial, sans-serif; + -fx-font-size: 11pt; + -fx-text-fill: black; + -fx-background-insets: 0 0 0 0, 0, 1, 2; +} + +.button:hover { + -fx-background-color: #3a3a3a; +} + +.button:pressed, .button:default:hover:pressed { + -fx-background-color: white; + -fx-text-fill: black; +} + +.button:focused { + -fx-border-color: white, white; + -fx-border-width: 1, 1; + -fx-border-style: solid, segments(1, 1); + -fx-border-radius: 0, 0; + -fx-border-insets: 1 1 1 1, 0; +} + +.button:disabled, .button:default:disabled { + -fx-opacity: 0.4; + -fx-background-color: #1d1d1d; + -fx-text-fill: black; +} + +.button:default { + -fx-background-color: -fx-focus-color; + -fx-text-fill: black; +} + +.button:default:hover { + -fx-background-color: black; +} + +.dialog-pane { + -fx-background-color: black; +} + +.dialog-pane > *.button-bar > *.container { + -fx-background-color: black; +} + +.dialog-pane > *.label.content { + -fx-font-size: 14px; + -fx-font-weight: bold; + -fx-text-fill: white; +} + +.dialog-pane:header *.header-panel { + -fx-background-color: black; +} + +.dialog-pane:header *.header-panel *.label { + -fx-font-size: 18px; + -fx-font-style: italic; + -fx-fill: white; + -fx-text-fill: white; +} + +.scroll-bar { /* Scroll bar column background color */ + -fx-background-color: #ced8dd; +} + +.scroll-bar .thumb { /* Scroll bar background color */ + -fx-background-color: #939a9e; + -fx-background-insets: 3; +} + +.scroll-bar .increment-button, .scroll-bar .decrement-button { + -fx-background-color: transparent; + -fx-padding: 0 0 0 0; +} + +.scroll-bar .increment-arrow, .scroll-bar .decrement-arrow { + -fx-shape: " "; +} + +.scroll-bar:vertical .increment-arrow, .scroll-bar:vertical .decrement-arrow { + -fx-padding: 1 8 1 8; +} + +.scroll-bar:horizontal .increment-arrow, .scroll-bar:horizontal .decrement-arrow { + -fx-padding: 8 1 8 1; +} + +#cardPane { + -fx-background-color: #37598e; + -fx-border-width: 0; +} + +#commandTypeLabel { + -fx-font-size: 11px; + -fx-text-fill: black; +} + +#commandTextField { /* Command box */ + -fx-background-color: transparent #383838 transparent #383838; + -fx-background-insets: 0; + -fx-border-color: black; + -fx-border-insets: 0; + -fx-border-width: 2.1; + -fx-font-family: "Andale Mono"; + -fx-font-size: 13pt; + -fx-text-fill: black; + -fx-prompt-text-fill: black; +} + +#filterField, #personListPanel, #personWebpage { + -fx-effect: innershadow(gaussian, black, 10, 0, 0, 0); +} + +#resultDisplay .content { + -fx-background-color: #c9bbbb; + -fx-background-radius: 0; +} + +#tags { + -fx-hgap: 7; + -fx-vgap: 3; +} + +#tags .label { + -fx-text-fill: white; + -fx-background-color: gray; + -fx-padding: 1 3 1 3; + -fx-border-radius: 2; + -fx-background-radius: 15; + -fx-font-size: 11; +} + +#calendarPlaceholder { + -fx-background-color: white; +} + +#commandBoxPlaceholder { + -fx-background-color: white; +} + +#resultDisplayPlaceholder { + -fx-background-color: white; +} + +#statusbarPlaceholder { + -fx-background-color: white; +} + +#personListPanelPlaceholder { + -fx-background-color: white; +} + +#taskListPanelPlaceholder { + -fx-background-color: white; +} diff --git a/src/main/resources/view/default.html b/src/main/resources/view/default.html index c49aa0f61682..07274af9c41d 100644 --- a/src/main/resources/view/default.html +++ b/src/main/resources/view/default.html @@ -1,7 +1,7 @@ - + diff --git a/src/test/data/XmlAddressBookStorageTest/invalidAndValidPersonAddressBook.xml b/src/test/data/XmlAddressBookStorageTest/invalidAndValidPersonAddressBook.xml index 41e411568a5f..1c93b3ab2a8d 100644 Binary files a/src/test/data/XmlAddressBookStorageTest/invalidAndValidPersonAddressBook.xml and b/src/test/data/XmlAddressBookStorageTest/invalidAndValidPersonAddressBook.xml differ diff --git a/src/test/data/XmlAddressBookStorageTest/invalidAndValidTaskAddressBook.xml b/src/test/data/XmlAddressBookStorageTest/invalidAndValidTaskAddressBook.xml new file mode 100644 index 000000000000..37be0d9abc30 --- /dev/null +++ b/src/test/data/XmlAddressBookStorageTest/invalidAndValidTaskAddressBook.xml @@ -0,0 +1,16 @@ + + + + + + exampleTask1 + 3h20m + 05/02/2018 3:20 + + + + exampleTask1 + 3h2m + 05/03/2018 3:20 + + diff --git a/src/test/data/XmlAddressBookStorageTest/invalidPersonAddressBook.xml b/src/test/data/XmlAddressBookStorageTest/invalidPersonAddressBook.xml index cfa128e72828..23d750c61aba 100644 Binary files a/src/test/data/XmlAddressBookStorageTest/invalidPersonAddressBook.xml and b/src/test/data/XmlAddressBookStorageTest/invalidPersonAddressBook.xml differ diff --git a/src/test/data/XmlAddressBookStorageTest/invalidTaskAddressBook.xml b/src/test/data/XmlAddressBookStorageTest/invalidTaskAddressBook.xml new file mode 100644 index 000000000000..7656f67615de --- /dev/null +++ b/src/test/data/XmlAddressBookStorageTest/invalidTaskAddressBook.xml @@ -0,0 +1,9 @@ + + + + + exampleTask1 + 3333333::: + 2018-05-02T03:20 + + diff --git a/src/test/data/XmlSerializableAddressBookTest/invalidTaskAddressBook.xml b/src/test/data/XmlSerializableAddressBookTest/invalidTaskAddressBook.xml new file mode 100644 index 000000000000..9237fe29cf80 --- /dev/null +++ b/src/test/data/XmlSerializableAddressBookTest/invalidTaskAddressBook.xml @@ -0,0 +1,10 @@ + + + + + + exampleDescription + 3h2m + 02/05/2018 03:20 + + diff --git a/src/test/data/XmlSerializableAddressBookTest/typicalPersonsAddressBook.xml b/src/test/data/XmlSerializableAddressBookTest/typicalPersonsAndTaskAddressBook.xml similarity index 62% rename from src/test/data/XmlSerializableAddressBookTest/typicalPersonsAddressBook.xml rename to src/test/data/XmlSerializableAddressBookTest/typicalPersonsAndTaskAddressBook.xml index c778cccc4c89..c35df69a74a3 100644 --- a/src/test/data/XmlSerializableAddressBookTest/typicalPersonsAddressBook.xml +++ b/src/test/data/XmlSerializableAddressBookTest/typicalPersonsAndTaskAddressBook.xml @@ -1,6 +1,6 @@ +{@code TypicalAddressBookCompiler#getTypicalAddressBook1()}--> Alice Pauline @@ -49,4 +49,32 @@ friends owesMoney + + Alice Pauline + Calculus page 24 + 2h0m + 01/10/2018 10:00 + + + Benson Meier + Math exam + 2h0m + 01/10/2018 14:30 + + + Carl Kurtz + + 1h20m + 31/12/2018 09:15 + + + grocery shopping + 1h0m + 25/04/2017 14:30 + + + yoga + 3h0m + 28/02/2019 14:30 + diff --git a/src/test/data/XmlSerializableAddressBookTest/typicalTasksAddressBook.xml b/src/test/data/XmlSerializableAddressBookTest/typicalTasksAddressBook.xml new file mode 100644 index 000000000000..28cf708ac178 --- /dev/null +++ b/src/test/data/XmlSerializableAddressBookTest/typicalTasksAddressBook.xml @@ -0,0 +1,26 @@ + + + + + + exampleTask1 + 3h20m + 02/05/2018T03:20 + + + exampleTask2 + 3h20m + 02/04/2018T13:20 + + + exampleTask3 + 3h20m + 02/06/2018T23:20 + + + exampleTask4 + 3h20m + 02/07/2018T23:20 + + diff --git a/src/test/data/XmlUtilTest/invalidTaskField.xml b/src/test/data/XmlUtilTest/invalidTaskField.xml new file mode 100644 index 000000000000..a7210034bb42 --- /dev/null +++ b/src/test/data/XmlUtilTest/invalidTaskField.xml @@ -0,0 +1,8 @@ + + + + + exampleTask1 + 3h20m + 2018-00-00T03:20 + diff --git a/src/test/data/XmlUtilTest/missingTaskField.xml b/src/test/data/XmlUtilTest/missingTaskField.xml new file mode 100644 index 000000000000..c829ca40a5b9 --- /dev/null +++ b/src/test/data/XmlUtilTest/missingTaskField.xml @@ -0,0 +1,7 @@ + + + + + exampleTask1 + 3:20 + diff --git a/src/test/data/XmlUtilTest/tempAddressBook.xml b/src/test/data/XmlUtilTest/tempAddressBook.xml index 41eeb8eb391a..2392f904bc79 100644 --- a/src/test/data/XmlUtilTest/tempAddressBook.xml +++ b/src/test/data/XmlUtilTest/tempAddressBook.xml @@ -1,4 +1,5 @@ + 1 @@ -12,4 +13,6 @@ Friends + + diff --git a/src/test/data/XmlUtilTest/validAddressBook.xml b/src/test/data/XmlUtilTest/validAddressBook.xml index 6265778674d3..1940fa62c08b 100644 --- a/src/test/data/XmlUtilTest/validAddressBook.xml +++ b/src/test/data/XmlUtilTest/validAddressBook.xml @@ -54,4 +54,20 @@ hans@example.com
chicago ave
+ + + exampleTask1 + 3h20m + 2018-05-02T03:20 + + + exampleTask2 + 3h20m + 2018-06-02T03:20 + + + exampleTask3 + 3h20m + 2018-07-02T03:20 + diff --git a/src/test/data/XmlUtilTest/validTask.xml b/src/test/data/XmlUtilTest/validTask.xml new file mode 100644 index 000000000000..9fee75285391 --- /dev/null +++ b/src/test/data/XmlUtilTest/validTask.xml @@ -0,0 +1,8 @@ + + + + + exampleTask1 + 3h20m + 02/03/2018T03:20 + diff --git a/src/test/java/guitests/guihandles/CalendarPanelHandle.java b/src/test/java/guitests/guihandles/CalendarPanelHandle.java new file mode 100644 index 000000000000..92a9da2bbdd0 --- /dev/null +++ b/src/test/java/guitests/guihandles/CalendarPanelHandle.java @@ -0,0 +1,42 @@ +package guitests.guihandles; + +import com.calendarfx.view.page.PageBase; + +import javafx.scene.Node; +import seedu.address.ui.CalendarPanel; + +//@@author ChoChihTun +/** + * A handler for the {@code CalendarPanel} of the UI + */ +public class CalendarPanelHandle extends NodeHandle { + + public static final String CALENDAR_PANEL_ID = "#calendarPlaceholder"; + + private CalendarPanel calendarPanel; + + public CalendarPanelHandle(Node calendarPanelNode) { + super(calendarPanelNode); + calendarPanel = new CalendarPanel(); + } + + public PageBase getDefaultCalendarViewPage() { + return calendarPanel.getRoot().getDayPage(); + } + + public PageBase getWeekViewPage() { + return calendarPanel.getRoot().getWeekPage(); + } + + public PageBase getMonthViewPage() { + return calendarPanel.getRoot().getMonthPage(); + } + + public PageBase getYearViewPage() { + return calendarPanel.getRoot().getYearPage(); + } + + public PageBase getCurrentCalendarViewPage() { + return calendarPanel.getRoot().getSelectedPage(); + } +} diff --git a/src/test/java/guitests/guihandles/MainWindowHandle.java b/src/test/java/guitests/guihandles/MainWindowHandle.java index 34e36054f4fd..b2c36a084239 100644 --- a/src/test/java/guitests/guihandles/MainWindowHandle.java +++ b/src/test/java/guitests/guihandles/MainWindowHandle.java @@ -12,7 +12,7 @@ public class MainWindowHandle extends StageHandle { private final CommandBoxHandle commandBox; private final StatusBarFooterHandle statusBarFooter; private final MainMenuHandle mainMenu; - private final BrowserPanelHandle browserPanel; + private final CalendarPanelHandle calendarPanel; public MainWindowHandle(Stage stage) { super(stage); @@ -22,7 +22,7 @@ public MainWindowHandle(Stage stage) { commandBox = new CommandBoxHandle(getChildNode(CommandBoxHandle.COMMAND_INPUT_FIELD_ID)); statusBarFooter = new StatusBarFooterHandle(getChildNode(StatusBarFooterHandle.STATUS_BAR_PLACEHOLDER)); mainMenu = new MainMenuHandle(getChildNode(MainMenuHandle.MENU_BAR_ID)); - browserPanel = new BrowserPanelHandle(getChildNode(BrowserPanelHandle.BROWSER_ID)); + calendarPanel = new CalendarPanelHandle(getChildNode(CalendarPanelHandle.CALENDAR_PANEL_ID)); } public PersonListPanelHandle getPersonListPanel() { @@ -45,7 +45,7 @@ public MainMenuHandle getMainMenu() { return mainMenu; } - public BrowserPanelHandle getBrowserPanel() { - return browserPanel; + public CalendarPanelHandle getCalendarPanel() { + return calendarPanel; } } diff --git a/src/test/java/guitests/guihandles/TaskCardHandle.java b/src/test/java/guitests/guihandles/TaskCardHandle.java new file mode 100644 index 000000000000..0e89edc9238b --- /dev/null +++ b/src/test/java/guitests/guihandles/TaskCardHandle.java @@ -0,0 +1,45 @@ +package guitests.guihandles; + +import javafx.scene.Node; +import javafx.scene.control.Label; + +/** + * Provides a handle to a task card in the task list panel. + */ +public class TaskCardHandle extends NodeHandle { + private static final String ID_FIELD_ID = "#id"; + private static final String DESCRIPTION_FIELD_ID = "#description"; + private static final String DURATION_FIELD_ID = "#duration"; + private static final String DATEANDTIME_FIELD_ID = "#dateandtime"; + + private final Label idLabel; + private final Label descriptionLabel; + private final Label durationLabel; + private final Label dateAndTimeLabel; + + public TaskCardHandle(Node cardNode) { + super(cardNode); + + this.idLabel = getChildNode(ID_FIELD_ID); + this.descriptionLabel = getChildNode(DESCRIPTION_FIELD_ID); + this.durationLabel = getChildNode(DURATION_FIELD_ID); + this.dateAndTimeLabel = getChildNode(DATEANDTIME_FIELD_ID); + } + + public String getId() { + return idLabel.getText(); + } + + public String getDescription() { + return descriptionLabel.getText(); + } + + public String getDuration() { + return durationLabel.getText(); + } + + public String getDateAndTime() { + return dateAndTimeLabel.getText(); + } + +} diff --git a/src/test/java/guitests/guihandles/TaskListPanelHandle.java b/src/test/java/guitests/guihandles/TaskListPanelHandle.java new file mode 100644 index 000000000000..b0176d267034 --- /dev/null +++ b/src/test/java/guitests/guihandles/TaskListPanelHandle.java @@ -0,0 +1,134 @@ +package guitests.guihandles; + +import java.util.List; +import java.util.Optional; + +import javafx.scene.control.ListView; +import seedu.address.model.Task; +import seedu.address.ui.TaskCard; + +/** + * Provides a handle for {@code PersonListPanel} containing the list of {@code PersonCard}. + */ +public class TaskListPanelHandle extends NodeHandle> { + public static final String TASK_LIST_VIEW_ID = "#taskListView"; + + private Optional lastRememberedSelectedTaskCard; + + public TaskListPanelHandle(ListView taskListPanelNode) { + super(taskListPanelNode); + } + + /** + * Returns a handle to the selected {@code TaskCardHandle}. + * A maximum of 1 item can be selected at any time. + * @throws AssertionError if no card is selected, or more than 1 card is selected. + */ + public TaskCardHandle getHandleToSelectedCard() { + List taskList = getRootNode().getSelectionModel().getSelectedItems(); + + if (taskList.size() != 1) { + throw new AssertionError("Task list size expected 1."); + } + + return new TaskCardHandle(taskList.get(0).getRoot()); + } + + /** + * Returns the index of the selected card. + */ + public int getSelectedCardIndex() { + return getRootNode().getSelectionModel().getSelectedIndex(); + } + + /** + * Returns true if a card is currently selected. + */ + public boolean isAnyCardSelected() { + List selectedCardsList = getRootNode().getSelectionModel().getSelectedItems(); + + if (selectedCardsList.size() > 1) { + throw new AssertionError("Card list size expected 0 or 1."); + } + + return !selectedCardsList.isEmpty(); + } + + /** + * Navigates the listview to display and select the person. + */ + public void navigateToCard(Task task) { + List cards = getRootNode().getItems(); + Optional matchingCard = cards.stream().filter(card -> card.task.equals(task)).findFirst(); + + if (!matchingCard.isPresent()) { + throw new IllegalArgumentException("Task does not exist."); + } + + guiRobot.interact(() -> { + getRootNode().scrollTo(matchingCard.get()); + getRootNode().getSelectionModel().select(matchingCard.get()); + }); + guiRobot.pauseForHuman(); + } + + /** + * Returns the task card handle of a task associated with the {@code index} in the list. + */ + public TaskCardHandle getTaskCardHandle(int index) { + return getTaskCardHandle(getRootNode().getItems().get(index).task); + } + + /** + * Returns the {@code TaskCardHandle} of the specified {@code task} in the list. + */ + public TaskCardHandle getTaskCardHandle(Task task) { + Optional handle = getRootNode().getItems().stream() + .filter(card -> card.task.equals(task)) + .map(card -> new TaskCardHandle(card.getRoot())) + .findFirst(); + return handle.orElseThrow(() -> new IllegalArgumentException("Task does not exist.")); + } + + /** + * Selects the {@code TaskCard} at {@code index} in the list. + */ + public void select(int index) { + getRootNode().getSelectionModel().select(index); + } + + /** + * Remembers the selected {@code TaskCard} in the list. + */ + public void rememberSelectedTaskCard() { + List selectedItems = getRootNode().getSelectionModel().getSelectedItems(); + + if (selectedItems.size() == 0) { + lastRememberedSelectedTaskCard = Optional.empty(); + } else { + lastRememberedSelectedTaskCard = Optional.of(selectedItems.get(0)); + } + } + + /** + * Returns true if the selected {@code PersonCard} is different from the value remembered by the most recent + * {@code rememberSelectedPersonCard()} call. + */ + public boolean isSelectedTaskCardChanged() { + List selectedItems = getRootNode().getSelectionModel().getSelectedItems(); + + if (selectedItems.size() == 0) { + return lastRememberedSelectedTaskCard.isPresent(); + } else { + return !lastRememberedSelectedTaskCard.isPresent() + || !lastRememberedSelectedTaskCard.get().equals(selectedItems.get(0)); + } + } + + /** + * Returns the size of the list. + */ + public int getListSize() { + return getRootNode().getItems().size(); + } +} diff --git a/src/test/java/seedu/address/commons/util/XmlUtilTest.java b/src/test/java/seedu/address/commons/util/XmlUtilTest.java index 56b6ef8f40d3..43e4f59bb831 100644 --- a/src/test/java/seedu/address/commons/util/XmlUtilTest.java +++ b/src/test/java/seedu/address/commons/util/XmlUtilTest.java @@ -17,11 +17,14 @@ import seedu.address.model.AddressBook; import seedu.address.storage.XmlAdaptedPerson; import seedu.address.storage.XmlAdaptedTag; +import seedu.address.storage.XmlAdaptedTask; import seedu.address.storage.XmlSerializableAddressBook; import seedu.address.testutil.AddressBookBuilder; import seedu.address.testutil.PersonBuilder; +import seedu.address.testutil.TaskBuilder; import seedu.address.testutil.TestUtil; +//@@author a-shakra public class XmlUtilTest { private static final String TEST_DATA_FOLDER = FileUtil.getPath("src/test/data/XmlUtilTest/"); @@ -31,8 +34,16 @@ public class XmlUtilTest { private static final File MISSING_PERSON_FIELD_FILE = new File(TEST_DATA_FOLDER + "missingPersonField.xml"); private static final File INVALID_PERSON_FIELD_FILE = new File(TEST_DATA_FOLDER + "invalidPersonField.xml"); private static final File VALID_PERSON_FILE = new File(TEST_DATA_FOLDER + "validPerson.xml"); + private static final File MISSING_TASK_FIELD_FILE = new File(TEST_DATA_FOLDER + "missingTaskField.xml"); + private static final File INVALID_TASK_FIELD_FILE = new File(TEST_DATA_FOLDER + "invalidTaskField.xml"); + private static final File VALID_TASK_FILE = new File(TEST_DATA_FOLDER + "validTask.xml"); private static final File TEMP_FILE = new File(TestUtil.getFilePathInSandboxFolder("tempAddressBook.xml")); + private static final String VALID_DURATION = "3h20m"; + private static final String VALID_DESCRIPTION = "exampleTask1"; + private static final String VALID_DATEANDTIME = "02/03/2018T03:20"; + private static final String INVALID_DATEANDTIME = "2018-00-00T03:20"; + //@@author private static final String INVALID_PHONE = "9482asf424"; private static final String VALID_NAME = "Hans Muster"; @@ -73,6 +84,7 @@ public void getDataFromFile_validFile_validResult() throws Exception { AddressBook dataFromFile = XmlUtil.getDataFromFile(VALID_FILE, XmlSerializableAddressBook.class).toModelType(); assertEquals(9, dataFromFile.getPersonList().size()); assertEquals(0, dataFromFile.getTagList().size()); + assertEquals(3, dataFromFile.getTaskList().size()); } @Test @@ -84,6 +96,17 @@ public void xmlAdaptedPersonFromFile_fileWithMissingPersonField_validResult() th assertEquals(expectedPerson, actualPerson); } + //@@author a-shakra + @Test + public void xmlAdaptedTaskFromFile_fileWithMissingTaskField_validResult() throws Exception { + XmlAdaptedTask actualTask = XmlUtil.getDataFromFile( + MISSING_TASK_FIELD_FILE, XmlAdaptedTaskWithRootElement.class); + XmlAdaptedTask expectedTask = new XmlAdaptedTask( + "exampleTask1", "3:20", null); + assertEquals(expectedTask, actualTask); + } + //@@author + @Test public void xmlAdaptedPersonFromFile_fileWithInvalidPersonField_validResult() throws Exception { XmlAdaptedPerson actualPerson = XmlUtil.getDataFromFile( @@ -93,6 +116,15 @@ public void xmlAdaptedPersonFromFile_fileWithInvalidPersonField_validResult() th assertEquals(expectedPerson, actualPerson); } + @Test + public void xmlAdaptedTaskFromFile_fileWithInvalidTaskField_validResult() throws Exception { + XmlAdaptedTask actualTask = XmlUtil.getDataFromFile( + INVALID_TASK_FIELD_FILE, XmlAdaptedTaskWithRootElement.class); + XmlAdaptedTask expectedTask = new XmlAdaptedTask( + VALID_DESCRIPTION, VALID_DURATION, INVALID_DATEANDTIME); + assertEquals(expectedTask, actualTask); + } + @Test public void xmlAdaptedPersonFromFile_fileWithValidPerson_validResult() throws Exception { XmlAdaptedPerson actualPerson = XmlUtil.getDataFromFile( @@ -102,6 +134,15 @@ public void xmlAdaptedPersonFromFile_fileWithValidPerson_validResult() throws Ex assertEquals(expectedPerson, actualPerson); } + @Test + public void xmlAdaptedTaskFromFile_fileWithValidTaskField_validResult() throws Exception { + XmlAdaptedTask actualTask = XmlUtil.getDataFromFile( + VALID_TASK_FILE, XmlAdaptedTaskWithRootElement.class); + XmlAdaptedTask expectedTask = new XmlAdaptedTask( + VALID_DESCRIPTION, VALID_DURATION, VALID_DATEANDTIME); + assertEquals(expectedTask, actualTask); + } + @Test public void saveDataToFile_nullFile_throwsNullPointerException() throws Exception { thrown.expect(NullPointerException.class); @@ -130,7 +171,8 @@ public void saveDataToFile_validFile_dataSaved() throws Exception { AddressBookBuilder builder = new AddressBookBuilder(new AddressBook()); dataToWrite = new XmlSerializableAddressBook( - builder.withPerson(new PersonBuilder().build()).withTag("Friends").build()); + builder.withPerson(new PersonBuilder().build()).withTag("Friends").withTask(new TaskBuilder() + .buildPersonalTask()).build()); XmlUtil.saveDataToFile(TEMP_FILE, dataToWrite); dataFromFile = XmlUtil.getDataFromFile(TEMP_FILE, XmlSerializableAddressBook.class); @@ -138,9 +180,14 @@ public void saveDataToFile_validFile_dataSaved() throws Exception { } /** - * Test class annotated with {@code XmlRootElement} to allow unmarshalling of .xml data to {@code XmlAdaptedPerson} - * objects. + * Test class annotated with {@code XmlRootElement} to allow unmarshalling of .xml data to {@code XmlAdaptedPerson, + * @code XmlAdaptedTask} objects. */ @XmlRootElement(name = "person") private static class XmlAdaptedPersonWithRootElement extends XmlAdaptedPerson {} + + //@@author a-shakra + @XmlRootElement(name = "tasks") + private static class XmlAdaptedTaskWithRootElement extends XmlAdaptedTask {} + //@@author } diff --git a/src/test/java/seedu/address/logic/LogicManagerTest.java b/src/test/java/seedu/address/logic/LogicManagerTest.java index 954c1fb04388..0e2b9bde4465 100644 --- a/src/test/java/seedu/address/logic/LogicManagerTest.java +++ b/src/test/java/seedu/address/logic/LogicManagerTest.java @@ -4,6 +4,7 @@ import static seedu.address.commons.core.Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX; import static seedu.address.commons.core.Messages.MESSAGE_UNKNOWN_COMMAND; +import org.junit.BeforeClass; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; @@ -16,6 +17,7 @@ import seedu.address.model.Model; import seedu.address.model.ModelManager; import seedu.address.model.UserPrefs; +import systemtests.SystemTestSetupHelper; public class LogicManagerTest { @@ -25,6 +27,11 @@ public class LogicManagerTest { private Model model = new ModelManager(); private Logic logic = new LogicManager(model); + @BeforeClass + public static void setupBeforeClass() { + SystemTestSetupHelper.initialize(); + } + @Test public void execute_invalidCommandFormat_throwsParseException() { String invalidCommand = "uicfhmowqewca"; @@ -52,6 +59,12 @@ public void getFilteredPersonList_modifyList_throwsUnsupportedOperationException logic.getFilteredPersonList().remove(0); } + @Test + public void getFilteredTaskList_modifyList_throwsUnsupportedOperationException() { + thrown.expect(UnsupportedOperationException.class); + logic.getFilteredTaskList().remove(0); + } + /** * Executes the command, confirms that no exceptions are thrown and that the result message is correct. * Also confirms that {@code expectedModel} is as specified. diff --git a/src/test/java/seedu/address/logic/commands/AddCommandIntegrationTest.java b/src/test/java/seedu/address/logic/commands/AddCommandIntegrationTest.java index d3ff5043e126..fd4b4cec60cc 100644 --- a/src/test/java/seedu/address/logic/commands/AddCommandIntegrationTest.java +++ b/src/test/java/seedu/address/logic/commands/AddCommandIntegrationTest.java @@ -2,7 +2,7 @@ import static seedu.address.logic.commands.CommandTestUtil.assertCommandFailure; import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess; -import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook; +import static seedu.address.testutil.typicaladdressbook.TypicalAddressBookCompiler.getTypicalAddressBook1; import org.junit.Before; import org.junit.Test; @@ -24,7 +24,7 @@ public class AddCommandIntegrationTest { @Before public void setUp() { - model = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + model = new ModelManager(getTypicalAddressBook1(), new UserPrefs()); } @Test diff --git a/src/test/java/seedu/address/logic/commands/AddCommandTest.java b/src/test/java/seedu/address/logic/commands/AddCommandTest.java index 461cf09d1217..f240ac865b70 100644 --- a/src/test/java/seedu/address/logic/commands/AddCommandTest.java +++ b/src/test/java/seedu/address/logic/commands/AddCommandTest.java @@ -4,17 +4,14 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; import java.util.ArrayList; import java.util.Arrays; -import java.util.function.Predicate; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; -import javafx.collections.ObservableList; import seedu.address.logic.CommandHistory; import seedu.address.logic.UndoRedoStack; import seedu.address.logic.commands.exceptions.CommandException; @@ -23,7 +20,7 @@ import seedu.address.model.ReadOnlyAddressBook; import seedu.address.model.person.Person; import seedu.address.model.person.exceptions.DuplicatePersonException; -import seedu.address.model.person.exceptions.PersonNotFoundException; +import seedu.address.testutil.ModelStub; import seedu.address.testutil.PersonBuilder; public class AddCommandTest { @@ -92,49 +89,6 @@ private AddCommand getAddCommandForPerson(Person person, Model model) { return command; } - /** - * A default model stub that have all of the methods failing. - */ - private class ModelStub implements Model { - @Override - public void addPerson(Person person) throws DuplicatePersonException { - fail("This method should not be called."); - } - - @Override - public void resetData(ReadOnlyAddressBook newData) { - fail("This method should not be called."); - } - - @Override - public ReadOnlyAddressBook getAddressBook() { - fail("This method should not be called."); - return null; - } - - @Override - public void deletePerson(Person target) throws PersonNotFoundException { - fail("This method should not be called."); - } - - @Override - public void updatePerson(Person target, Person editedPerson) - throws DuplicatePersonException { - fail("This method should not be called."); - } - - @Override - public ObservableList getFilteredPersonList() { - fail("This method should not be called."); - return null; - } - - @Override - public void updateFilteredPersonList(Predicate predicate) { - fail("This method should not be called."); - } - } - /** * A Model stub that always throw a DuplicatePersonException when trying to add a person. */ @@ -167,5 +121,4 @@ public ReadOnlyAddressBook getAddressBook() { return new AddressBook(); } } - } diff --git a/src/test/java/seedu/address/logic/commands/AddPersonalTaskCommandTest.java b/src/test/java/seedu/address/logic/commands/AddPersonalTaskCommandTest.java new file mode 100644 index 000000000000..c6c22c889fec --- /dev/null +++ b/src/test/java/seedu/address/logic/commands/AddPersonalTaskCommandTest.java @@ -0,0 +1,145 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import static seedu.address.commons.core.Messages.MESSAGE_TASK_TIMING_CLASHES; +import static seedu.address.logic.commands.CommandTestUtil.VALID_DATE_TIME_AMY; +import static seedu.address.logic.commands.CommandTestUtil.VALID_DATE_TIME_BOB; +import static seedu.address.logic.commands.CommandTestUtil.VALID_DURATION_AMY; +import static seedu.address.logic.commands.CommandTestUtil.VALID_TASK_DESC_AMY; +import static seedu.address.testutil.TaskUtil.FORMATTER; +import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.Arrays; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import seedu.address.logic.CommandHistory; +import seedu.address.logic.UndoRedoStack; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.AddressBook; +import seedu.address.model.Model; +import seedu.address.model.ReadOnlyAddressBook; +import seedu.address.model.Task; +import seedu.address.model.personal.PersonalTask; +import seedu.address.model.task.exceptions.TimingClashException; +import seedu.address.testutil.ModelStub; +import seedu.address.testutil.TaskBuilder; + +//@@author yungyung04 +public class AddPersonalTaskCommandTest { + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @Test + public void constructor_nullTask_throwsNullPointerException() { + thrown.expect(NullPointerException.class); + new AddPersonalTaskCommand(null); + } + + @Test + public void execute_taskAcceptedByModel_addSuccessful() throws Exception { + ModelStubAcceptingPersonalTaskAdded modelStub = new ModelStubAcceptingPersonalTaskAdded(); + PersonalTask validTask = new TaskBuilder().buildPersonalTask(); + + CommandResult commandResult = getAddPersonalTaskCommandForTask(validTask, modelStub).execute(); + + assertEquals(String.format(AddPersonalTaskCommand.MESSAGE_SUCCESS, validTask), commandResult.feedbackToUser); + assertEquals(Arrays.asList(validTask), modelStub.tasksAdded); + } + + @Test + public void execute_clashingTask_throwsCommandException() throws Exception { + ModelStub modelStub = new ModelStubThrowingTimingClashException(); + PersonalTask validTask = new TaskBuilder().buildPersonalTask(); + + thrown.expect(CommandException.class); + thrown.expectMessage(MESSAGE_TASK_TIMING_CLASHES); + + getAddPersonalTaskCommandForTask(validTask, modelStub).execute(); + } + + @Test + public void equals() { + PersonalTask firstPersonalTask = new TaskBuilder().withDateTime(VALID_DATE_TIME_AMY).buildPersonalTask(); + PersonalTask secondPersonalTask = new TaskBuilder().withDateTime(VALID_DATE_TIME_BOB).buildPersonalTask(); + + AddPersonalTaskCommand addFirstTask = new AddPersonalTaskCommand(firstPersonalTask); + AddPersonalTaskCommand addFirstTaskCopy = new AddPersonalTaskCommand(firstPersonalTask); + AddPersonalTaskCommand addSecondTask = new AddPersonalTaskCommand(secondPersonalTask); + + LocalDateTime tuitionDateTime = LocalDateTime.parse(VALID_DATE_TIME_AMY, FORMATTER); + AddTuitionTaskCommand addTuitionTask = new AddTuitionTaskCommand( + INDEX_FIRST_PERSON, tuitionDateTime, VALID_DURATION_AMY, VALID_TASK_DESC_AMY); + + // same object -> returns true + assertTrue(addFirstTask.equals(addFirstTask)); + + // same values -> returns true + assertTrue(addFirstTask.equals(addFirstTaskCopy)); + + // different types -> returns false + assertFalse(addFirstTask.equals(1)); + + // null -> returns false + assertFalse(addFirstTask.equals(null)); + + // different task type -> returns false + assertFalse(addFirstTask.equals(addTuitionTask)); + + // different detail -> returns false + assertFalse(addFirstTask.equals(addSecondTask)); + } + + /** + * Generates a new AddPersonalTaskCommand with the details of the given personal task. + */ + private AddPersonalTaskCommand getAddPersonalTaskCommandForTask(PersonalTask task, Model model) { + AddPersonalTaskCommand command = new AddPersonalTaskCommand(task); + command.setData(model, new CommandHistory(), new UndoRedoStack()); + return command; + } + + /** + * A Model stub that always throw a TimingClashException when trying to add a task. + */ + private class ModelStubThrowingTimingClashException extends ModelStub { + @Override + public void addTask(Task task) throws TimingClashException { + throw new TimingClashException(); + } + + + + @Override + public ReadOnlyAddressBook getAddressBook() { + return new AddressBook(); + } + } + + /** + * A Model stub that always accept the task being added. + */ + private class ModelStubAcceptingPersonalTaskAdded extends ModelStub { + final ArrayList tasksAdded = new ArrayList<>(); + + @Override + public void addTask(Task task) throws TimingClashException { + requireNonNull(task); + tasksAdded.add(task); + } + + @Override + public ReadOnlyAddressBook getAddressBook() { + return new AddressBook(); + } + } +} diff --git a/src/test/java/seedu/address/logic/commands/AddTuitionTaskCommandTest.java b/src/test/java/seedu/address/logic/commands/AddTuitionTaskCommandTest.java new file mode 100644 index 000000000000..1d22714699c2 --- /dev/null +++ b/src/test/java/seedu/address/logic/commands/AddTuitionTaskCommandTest.java @@ -0,0 +1,130 @@ +package seedu.address.logic.commands; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static seedu.address.commons.core.Messages.MESSAGE_TASK_TIMING_CLASHES; +import static seedu.address.logic.commands.CommandTestUtil.VALID_DATE_TIME_AMY; +import static seedu.address.logic.commands.CommandTestUtil.VALID_DATE_TIME_BOB; +import static seedu.address.logic.commands.CommandTestUtil.VALID_DURATION_AMY; +import static seedu.address.logic.commands.CommandTestUtil.VALID_DURATION_BOB; +import static seedu.address.logic.commands.CommandTestUtil.VALID_TASK_DESC_AMY; +import static seedu.address.logic.commands.CommandTestUtil.VALID_TASK_DESC_BOB; +import static seedu.address.logic.commands.CommandTestUtil.assertCommandFailure; +import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess; +import static seedu.address.testutil.TaskUtil.FORMATTER; +import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON; +import static seedu.address.testutil.TypicalIndexes.INDEX_SECOND_PERSON; +import static seedu.address.testutil.TypicalIndexes.INDEX_THIRD_PERSON; +import static seedu.address.testutil.typicaladdressbook.TypicalAddressBookCompiler.getTypicalAddressBook2; +import static seedu.address.testutil.typicaladdressbook.TypicalTasks.TASK_AMY; + +import java.time.LocalDateTime; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.logic.CommandHistory; +import seedu.address.logic.UndoRedoStack; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.ModelManager; +import seedu.address.model.UserPrefs; +import seedu.address.testutil.TaskBuilder; + +//@@author yungyung04 +public class AddTuitionTaskCommandTest { + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + private Model model = new ModelManager(getTypicalAddressBook2(), new UserPrefs()); + private LocalDateTime taskDateTimeAmy = LocalDateTime.parse(VALID_DATE_TIME_AMY, FORMATTER); + + @Test + public void constructor_nullTaskDetail_throwsNullPointerException() { + //one of the other 3 task details is null. + thrown.expect(NullPointerException.class); + new AddTuitionTaskCommand(INDEX_FIRST_PERSON, taskDateTimeAmy, VALID_DURATION_AMY, null); + } + + @Test + public void execute_taskAcceptedByModel_addSuccessful() throws Exception { + Model expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs()); + + AddTuitionTaskCommand addTuitionAmy = getAddTuitionTaskCommandForTask( + INDEX_THIRD_PERSON, taskDateTimeAmy, VALID_DURATION_AMY, VALID_TASK_DESC_AMY); + + String expectedMessage = String.format(AddTuitionTaskCommand.MESSAGE_SUCCESS, TASK_AMY); + expectedModel.addTask(TASK_AMY); + + assertCommandSuccess(addTuitionAmy, model, expectedMessage, expectedModel); + } + + @Test + public void execute_invalidIndex_throwsCommandException() throws Exception { + Index outOfBoundIndex = Index.fromOneBased(model.getFilteredTaskList().size() + 1); + AddTuitionTaskCommand command = getAddTuitionTaskCommandForTask(outOfBoundIndex, taskDateTimeAmy, + VALID_DURATION_AMY, VALID_TASK_DESC_AMY); + assertCommandFailure(command, model, Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + } + + @Test + public void execute_clashingTask_throwsCommandException() throws Exception { + thrown.expect(CommandException.class); + thrown.expectMessage(MESSAGE_TASK_TIMING_CLASHES); + + getAddTuitionTaskCommandForTask(INDEX_THIRD_PERSON, taskDateTimeAmy, VALID_DURATION_AMY, + VALID_TASK_DESC_AMY).execute(); + + getAddTuitionTaskCommandForTask(INDEX_FIRST_PERSON, taskDateTimeAmy, VALID_DURATION_AMY, + VALID_TASK_DESC_AMY).execute(); + } + + + @Test + public void equals() { + LocalDateTime taskDateTimeBob = LocalDateTime.parse(VALID_DATE_TIME_BOB, FORMATTER); + + AddTuitionTaskCommand addTuitionAmy = getAddTuitionTaskCommandForTask( + INDEX_THIRD_PERSON, taskDateTimeAmy, VALID_DURATION_AMY, VALID_TASK_DESC_AMY); + AddTuitionTaskCommand addTuitionAmyCopy = getAddTuitionTaskCommandForTask( + INDEX_THIRD_PERSON, taskDateTimeAmy, VALID_DURATION_AMY, VALID_TASK_DESC_AMY); + AddTuitionTaskCommand addTuitionBob = new AddTuitionTaskCommand( + INDEX_SECOND_PERSON, taskDateTimeBob, VALID_DURATION_BOB, VALID_TASK_DESC_BOB); + + // an AddPersonalTaskCommand object with same task details as addTuitionAmy + AddPersonalTaskCommand addPersonalTask = + new AddPersonalTaskCommand(new TaskBuilder(TASK_AMY).buildPersonalTask()); + + // same value -> returns true + assertTrue(addTuitionAmy.equals(addTuitionAmyCopy)); + + // same object -> returns true + assertTrue(addTuitionAmy.equals(addTuitionAmy)); + + // different types -> returns false + assertFalse(addTuitionAmy.equals(1)); + + // null -> returns false + assertFalse(addTuitionAmy.equals(null)); + + // different task type -> returns false + assertFalse(addTuitionAmy.equals(addPersonalTask)); + + // different detail -> returns false + assertFalse(addTuitionAmy.equals(addTuitionBob)); + } + + /** + * Generates a new AddTuitionTaskCommand with the details of the given tuition task. + */ + private AddTuitionTaskCommand getAddTuitionTaskCommandForTask(Index tuteeIndex, LocalDateTime taskDateTime, + String duration, String description) { + AddTuitionTaskCommand command = new AddTuitionTaskCommand(tuteeIndex, taskDateTime, duration, description); + command.setData(model, new CommandHistory(), new UndoRedoStack()); + return command; + } +} diff --git a/src/test/java/seedu/address/logic/commands/AddTuteeCommandTest.java b/src/test/java/seedu/address/logic/commands/AddTuteeCommandTest.java new file mode 100644 index 000000000000..7273958f6e8c --- /dev/null +++ b/src/test/java/seedu/address/logic/commands/AddTuteeCommandTest.java @@ -0,0 +1,138 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.util.ArrayList; +import java.util.Arrays; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import seedu.address.logic.CommandHistory; +import seedu.address.logic.UndoRedoStack; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.AddressBook; +import seedu.address.model.Model; +import seedu.address.model.ReadOnlyAddressBook; +import seedu.address.model.person.Person; +import seedu.address.model.person.exceptions.DuplicatePersonException; +import seedu.address.model.tutee.Tutee; +import seedu.address.testutil.ModelStub; +import seedu.address.testutil.TuteeBuilder; + +//@@author ChoChihTun +/** + * Contains integration tests (interaction with the Model) for {@code AddTuteeCommand}. + */ +public class AddTuteeCommandTest { + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @Test + public void constructor_nullTutee_throwsNullPointerException() { + thrown.expect(NullPointerException.class); + new AddTuteeCommand(null); + } + + @Test + public void execute_tuteeAcceptedByModel_addSuccessful() throws Exception { + ModelStubAcceptingPersonAdded modelStub = new ModelStubAcceptingPersonAdded(); + Tutee validTutee = new TuteeBuilder().build(); + + CommandResult commandResult = getAddTuteeCommandForTutee(validTutee, modelStub).execute(); + + assertEquals(String.format(AddTuteeCommand.MESSAGE_SUCCESS, validTutee), commandResult.feedbackToUser); + assertEquals(Arrays.asList(validTutee), modelStub.personsAdded); + } + + @Test + public void execute_duplicatePerson_throwsCommandException() throws Exception { + ModelStub modelStub = new ModelStubThrowingDuplicatePersonException(); + Tutee validTutee = new TuteeBuilder().build(); + + thrown.expect(CommandException.class); + thrown.expectMessage(AddTuteeCommand.MESSAGE_DUPLICATE_PERSON); + + getAddTuteeCommandForTutee(validTutee, modelStub).execute(); + } + + @Test + public void equals_validArgs_returnsTrue() { + Tutee alice = new TuteeBuilder().withName("Alice").build(); + AddTuteeCommand addAliceCommand = new AddTuteeCommand(alice); + + // same object + assertTrue(addAliceCommand.equals(addAliceCommand)); + + // objects with same value + AddTuteeCommand addAliceCommandCopy = new AddTuteeCommand(alice); + assertTrue(addAliceCommand.equals(addAliceCommandCopy)); + } + + @Test + public void equals_invalidArgs_returnsFalse() { + Tutee alice = new TuteeBuilder().withName("Alice").build(); + Tutee bob = new TuteeBuilder().withName("Bob").build(); + AddTuteeCommand addAliceCommand = new AddTuteeCommand(alice); + AddTuteeCommand addBobCommand = new AddTuteeCommand(bob); + + // null + assertFalse(addAliceCommand.equals(null)); + + // wrong parameter data type + assertFalse(addAliceCommand.equals(1)); + + // different tutee + assertFalse(addAliceCommand.equals(addBobCommand)); + } + + /** + * Generates a new AddTuteeCommand with the details of the given tutee. + */ + private AddTuteeCommand getAddTuteeCommandForTutee(Tutee tutee, Model model) { + AddTuteeCommand command = new AddTuteeCommand(tutee); + command.setData(model, new CommandHistory(), new UndoRedoStack()); + return command; + } + + /** + * A Model stub that always throw a DuplicatePersonException when trying to add a person. + */ + private class ModelStubThrowingDuplicatePersonException extends ModelStub { + @Override + // A tutee is a person + public void addPerson(Person person) throws DuplicatePersonException { + throw new DuplicatePersonException(); + } + + @Override + public ReadOnlyAddressBook getAddressBook() { + return new AddressBook(); + } + } + + /** + * A Model stub that always accept the person being added. + */ + private class ModelStubAcceptingPersonAdded extends ModelStub { + final ArrayList personsAdded = new ArrayList<>(); + + @Override + // A tutee is a person + public void addPerson(Person person) throws DuplicatePersonException { + requireNonNull(person); + personsAdded.add(person); + } + + @Override + public ReadOnlyAddressBook getAddressBook() { + return new AddressBook(); + } + } + +} diff --git a/src/test/java/seedu/address/logic/commands/ClearCommandTest.java b/src/test/java/seedu/address/logic/commands/ClearCommandTest.java index 445ba20f9b00..505ceb436e17 100644 --- a/src/test/java/seedu/address/logic/commands/ClearCommandTest.java +++ b/src/test/java/seedu/address/logic/commands/ClearCommandTest.java @@ -1,7 +1,7 @@ package seedu.address.logic.commands; import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess; -import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook; +import static seedu.address.testutil.typicaladdressbook.TypicalAddressBookCompiler.getTypicalAddressBook1; import org.junit.Test; @@ -21,7 +21,7 @@ public void execute_emptyAddressBook_success() { @Test public void execute_nonEmptyAddressBook_success() { - Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + Model model = new ModelManager(getTypicalAddressBook1(), new UserPrefs()); assertCommandSuccess(prepareCommand(model), model, ClearCommand.MESSAGE_SUCCESS, model); } diff --git a/src/test/java/seedu/address/logic/commands/CommandTestUtil.java b/src/test/java/seedu/address/logic/commands/CommandTestUtil.java index 9a5679cc29b6..d8fe88546658 100644 --- a/src/test/java/seedu/address/logic/commands/CommandTestUtil.java +++ b/src/test/java/seedu/address/logic/commands/CommandTestUtil.java @@ -4,11 +4,16 @@ import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_EDUCATION_LEVEL; import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; +import static seedu.address.logic.parser.CliSyntax.PREFIX_GRADE; 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_SCHOOL; +import static seedu.address.logic.parser.CliSyntax.PREFIX_SUBJECT; import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; +import java.time.LocalDateTime; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -19,9 +24,11 @@ import seedu.address.logic.commands.exceptions.CommandException; import seedu.address.model.AddressBook; import seedu.address.model.Model; +import seedu.address.model.Task; import seedu.address.model.person.NameContainsKeywordsPredicate; import seedu.address.model.person.Person; import seedu.address.model.person.exceptions.PersonNotFoundException; +import seedu.address.model.task.exceptions.TaskNotFoundException; import seedu.address.testutil.EditPersonDescriptorBuilder; /** @@ -31,14 +38,38 @@ public class CommandTestUtil { public static final String VALID_NAME_AMY = "Amy Bee"; public static final String VALID_NAME_BOB = "Bob Choo"; + public static final String VALID_NAME_ROBERT = "Robert Anderson"; public static final String VALID_PHONE_AMY = "11111111"; public static final String VALID_PHONE_BOB = "22222222"; + public static final String VALID_PHONE_ROBERT = "33333333"; public static final String VALID_EMAIL_AMY = "amy@example.com"; public static final String VALID_EMAIL_BOB = "bob@example.com"; + public static final String VALID_EMAIL_ROBERT = "robert@example.com"; public static final String VALID_ADDRESS_AMY = "Block 312, Amy Street 1"; public static final String VALID_ADDRESS_BOB = "Block 123, Bobby Street 3"; + public static final String VALID_ADDRESS_ROBERT = "Block 123, Robert Street 3"; public static final String VALID_TAG_HUSBAND = "husband"; public static final String VALID_TAG_FRIEND = "friend"; + public static final String VALID_DATE_TIME_AMY = "21/01/2011 11:11"; + public static final String VALID_DATE_TIME_BOB = "21/02/2013 12:01"; + public static final String VALID_DURATION_AMY = "1h11m"; + public static final String VALID_DURATION_BOB = "1h45m"; + public static final String VALID_TASK_DESC_AMY = "tuition homework"; + public static final String VALID_TASK_DESC_BOB = "exam preparation"; + public static final String VALID_EMPTY_TASK_DESC = ""; + public static final String VALID_TASK_WITHOUT_DESC_AMY = VALID_DATE_TIME_AMY + " " + VALID_DURATION_AMY; + public static final String VALID_TASK_WITHOUT_DESC_BOB = VALID_DATE_TIME_BOB + " " + VALID_DURATION_BOB; + public static final String VALID_TASK_WITH_DESC_AMY = VALID_TASK_WITHOUT_DESC_AMY + " " + VALID_TASK_DESC_AMY; + public static final String VALID_TASK_WITH_DESC_BOB = VALID_TASK_WITHOUT_DESC_AMY + " " + VALID_TASK_DESC_AMY; + public static final String VALID_SUBJECT_AMY = "economics"; + public static final String VALID_SUBJECT_BOB = "mathematics"; + public static final String VALID_GRADE_AMY = "B+"; + public static final String VALID_GRADE_BOB = "A1"; + public static final String VALID_EDUCATION_LEVEL_AMY = "secondary"; + public static final String VALID_EDUCATION_LEVEL_BOB = "junior college"; + public static final String VALID_EDUCATION_LEVEL_ROBERT = "primary"; + public static final String VALID_SCHOOL_AMY = "nan hua high school"; + public static final String VALID_SCHOOL_BOB = "victoria junior college"; public static final String NAME_DESC_AMY = " " + PREFIX_NAME + VALID_NAME_AMY; public static final String NAME_DESC_BOB = " " + PREFIX_NAME + VALID_NAME_BOB; @@ -48,6 +79,14 @@ public class CommandTestUtil { public static final String EMAIL_DESC_BOB = " " + PREFIX_EMAIL + VALID_EMAIL_BOB; public static final String ADDRESS_DESC_AMY = " " + PREFIX_ADDRESS + VALID_ADDRESS_AMY; public static final String ADDRESS_DESC_BOB = " " + PREFIX_ADDRESS + VALID_ADDRESS_BOB; + public static final String SUBJECT_DESC_AMY = " " + PREFIX_SUBJECT + VALID_SUBJECT_AMY; + public static final String SUBJECT_DESC_BOB = " " + PREFIX_SUBJECT + VALID_SUBJECT_BOB; + public static final String GRADE_DESC_AMY = " " + PREFIX_GRADE + VALID_GRADE_AMY; + public static final String GRADE_DESC_BOB = " " + PREFIX_GRADE + VALID_GRADE_BOB; + public static final String EDUCATION_LEVEL_DESC_AMY = " " + PREFIX_EDUCATION_LEVEL + VALID_EDUCATION_LEVEL_AMY; + public static final String EDUCATION_LEVEL_DESC_BOB = " " + PREFIX_EDUCATION_LEVEL + VALID_EDUCATION_LEVEL_BOB; + public static final String SCHOOL_DESC_AMY = " " + PREFIX_SCHOOL + VALID_SCHOOL_AMY; + public static final String SCHOOL_DESC_BOB = " " + PREFIX_SCHOOL + VALID_SCHOOL_BOB; public static final String TAG_DESC_FRIEND = " " + PREFIX_TAG + VALID_TAG_FRIEND; public static final String TAG_DESC_HUSBAND = " " + PREFIX_TAG + VALID_TAG_HUSBAND; @@ -55,8 +94,15 @@ public class CommandTestUtil { public static final String INVALID_PHONE_DESC = " " + PREFIX_PHONE + "911a"; // 'a' not allowed in phones public static final String INVALID_EMAIL_DESC = " " + PREFIX_EMAIL + "bob!yahoo"; // missing '@' symbol public static final String INVALID_ADDRESS_DESC = " " + PREFIX_ADDRESS; // empty string not allowed for addresses + public static final String INVALID_SUBJECT_DESC = " " + PREFIX_SUBJECT + "economics1"; // '1' not allowed in subject + public static final String INVALID_GRADE_DESC = " " + PREFIX_GRADE + "+B"; // should start with alphabet + // only 'primary', 'secondary' and 'junior college' are valid + public static final String INVALID_EDUCATION_LEVEL = " " + PREFIX_EDUCATION_LEVEL + "university"; + public static final String INVALID_SCHOOL = " " + PREFIX_SCHOOL + "hanyang@1"; // should only be alphabetic public static final String INVALID_TAG_DESC = " " + PREFIX_TAG + "hubby*"; // '*' not allowed in tags + public static final String INVALID_TUTEE_TAG_DESC = " " + PREFIX_TAG + "Tutee"; // 'tutee' tag not allowed in person + public static final String TUTEE_TAG = "Tutee"; public static final String PREAMBLE_WHITESPACE = "\t \r \n"; public static final String PREAMBLE_NON_EMPTY = "NonEmptyPreamble"; @@ -78,7 +124,7 @@ public class CommandTestUtil { * - the {@code actualModel} matches {@code expectedModel} */ public static void assertCommandSuccess(Command command, Model actualModel, String expectedMessage, - Model expectedModel) { + Model expectedModel) { try { CommandResult result = command.execute(); assertEquals(expectedMessage, result.feedbackToUser); @@ -111,7 +157,7 @@ public static void assertCommandFailure(Command command, Model actualModel, Stri } /** - * Updates {@code model}'s filtered list to show only the person at the given {@code targetIndex} in the + * Updates {@code model}'s filtered persons list to show only the person at the given {@code targetIndex} in the * {@code model}'s address book. */ public static void showPersonAtIndex(Model model, Index targetIndex) { @@ -124,6 +170,35 @@ public static void showPersonAtIndex(Model model, Index targetIndex) { assertEquals(1, model.getFilteredPersonList().size()); } + /** + * Updates {@code model}'s filtered tasks list to show only the task 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 LocalDateTime taskDateTime = task.getTaskDateTime(); + model.updateFilteredTaskList(t -> t.getTaskDateTime().equals(taskDateTime)); + + assertEquals(1, model.getFilteredTaskList().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[] splitName = task.getDescription().split("\\s+"); + model.updateFilteredTaskList(new TaskContainsKeywordsPredicate(Arrays.asList(splitName[0]))); + + assertEquals(1, model.getFilteredTaskList().size()); + } +*/ /** * Deletes the first person in {@code model}'s filtered list from {@code model}'s address book. */ @@ -136,6 +211,18 @@ public static void deleteFirstPerson(Model model) { } } + /** + * Deletes the first person in {@code model}'s filtered list from {@code model}'s address book. + */ + public static void deleteFirstTask(Model model) { + Task firstTask = model.getFilteredTaskList().get(0); + try { + model.deleteTask(firstTask); + } catch (TaskNotFoundException pnfe) { + throw new AssertionError("Task in filtered list must exist in model.", pnfe); + } + } + /** * Returns an {@code UndoCommand} with the given {@code model} and {@code undoRedoStack} set. */ diff --git a/src/test/java/seedu/address/logic/commands/DeleteCommandTest.java b/src/test/java/seedu/address/logic/commands/DeleteCommandTest.java index 866e6a9be32a..7333b4a6b255 100644 --- a/src/test/java/seedu/address/logic/commands/DeleteCommandTest.java +++ b/src/test/java/seedu/address/logic/commands/DeleteCommandTest.java @@ -10,7 +10,7 @@ import static seedu.address.logic.commands.CommandTestUtil.showPersonAtIndex; import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON; import static seedu.address.testutil.TypicalIndexes.INDEX_SECOND_PERSON; -import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook; +import static seedu.address.testutil.typicaladdressbook.TypicalAddressBookCompiler.getTypicalAddressBook1; import org.junit.Test; @@ -29,7 +29,7 @@ */ public class DeleteCommandTest { - private Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + private Model model = new ModelManager(getTypicalAddressBook1(), new UserPrefs()); @Test public void execute_validIndexUnfilteredList_success() throws Exception { 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 000000000000..f3642424c5bf --- /dev/null +++ b/src/test/java/seedu/address/logic/commands/DeleteTaskCommandTest.java @@ -0,0 +1,195 @@ +package seedu.address.logic.commands; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertTrue; +import static seedu.address.logic.commands.CommandTestUtil.assertCommandFailure; +import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess; +import static seedu.address.logic.commands.CommandTestUtil.prepareRedoCommand; +import static seedu.address.logic.commands.CommandTestUtil.prepareUndoCommand; +import static seedu.address.logic.commands.CommandTestUtil.showTaskAtIndex; +import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_TASK; +import static seedu.address.testutil.TypicalIndexes.INDEX_SECOND_TASK; +import static seedu.address.testutil.typicaladdressbook.TypicalAddressBookCompiler.getTypicalAddressBook1; + +import org.junit.Test; + +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.logic.CommandHistory; +import seedu.address.logic.UndoRedoStack; +import seedu.address.model.Model; +import seedu.address.model.ModelManager; +import seedu.address.model.Task; +import seedu.address.model.UserPrefs; + +//@@author yungyung04 +/** + * Contains integration tests (interaction with the Model, UndoCommand and RedoCommand) and unit tests for + * {@code DeleteTaskCommand}. + */ +public class DeleteTaskCommandTest { + + private Model model = new ModelManager(getTypicalAddressBook1(), new UserPrefs()); + + @Test + public void execute_validIndexUnfilteredList_success() throws Exception { + Task taskToDelete = model.getFilteredTaskList().get(INDEX_FIRST_TASK.getZeroBased()); + DeleteTaskCommand deleteTaskCommand = prepareCommand(INDEX_FIRST_TASK); + + String expectedMessage = String.format(DeleteTaskCommand.MESSAGE_SUCCESS, taskToDelete); + + ModelManager expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs()); + expectedModel.deleteTask(taskToDelete); + + assertCommandSuccess(deleteTaskCommand, model, expectedMessage, expectedModel); + } + + @Test + public void execute_invalidIndexUnfilteredList_throwsCommandException() throws Exception { + Index outOfBoundIndex = Index.fromOneBased(model.getFilteredTaskList().size() + 1); + DeleteTaskCommand deleteTaskCommand = prepareCommand(outOfBoundIndex); + + assertCommandFailure(deleteTaskCommand, model, Messages.MESSAGE_INVALID_TASK_DISPLAYED_INDEX); + } + + @Test + public void execute_validIndexFilteredList_success() throws Exception { + showTaskAtIndex(model, INDEX_FIRST_TASK); + + Task taskToDelete = model.getFilteredTaskList().get(INDEX_FIRST_TASK.getZeroBased()); + DeleteTaskCommand deleteTaskCommand = prepareCommand(INDEX_FIRST_TASK); + + String expectedMessage = String.format(DeleteTaskCommand.MESSAGE_SUCCESS, taskToDelete); + + Model expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs()); + expectedModel.deleteTask(taskToDelete); + showNoTask(expectedModel); + + assertCommandSuccess(deleteTaskCommand, model, expectedMessage, expectedModel); + } + + @Test + public void execute_invalidIndexFilteredList_throwsCommandException() { + showTaskAtIndex(model, INDEX_FIRST_TASK); + + Index outOfBoundIndex = INDEX_SECOND_TASK; + // ensures that outOfBoundIndex is still in bounds of address book list + assertTrue(outOfBoundIndex.getZeroBased() < model.getAddressBook().getTaskList().size()); + + DeleteTaskCommand deleteTaskCommand = prepareCommand(outOfBoundIndex); + + assertCommandFailure(deleteTaskCommand, model, Messages.MESSAGE_INVALID_TASK_DISPLAYED_INDEX); + } + + @Test + public void executeUndoRedo_validIndexUnfilteredList_success() throws Exception { + UndoRedoStack undoRedoStack = new UndoRedoStack(); + UndoCommand undoCommand = prepareUndoCommand(model, undoRedoStack); + RedoCommand redoCommand = prepareRedoCommand(model, undoRedoStack); + Task taskToDelete = model.getFilteredTaskList().get(INDEX_FIRST_TASK.getZeroBased()); + DeleteTaskCommand deleteTaskCommand = prepareCommand(INDEX_FIRST_TASK); + Model expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs()); + + // delete -> first task deleted + deleteTaskCommand.execute(); + undoRedoStack.push(deleteTaskCommand); + + // undo -> reverts addressbook back to previous state and filtered task list to show all tasks + assertCommandSuccess(undoCommand, model, UndoCommand.MESSAGE_SUCCESS, expectedModel); + + // redo -> same first task deleted again + expectedModel.deleteTask(taskToDelete); + assertCommandSuccess(redoCommand, model, RedoCommand.MESSAGE_SUCCESS, expectedModel); + } + + @Test + public void executeUndoRedo_invalidIndexUnfilteredList_failure() { + UndoRedoStack undoRedoStack = new UndoRedoStack(); + UndoCommand undoCommand = prepareUndoCommand(model, undoRedoStack); + RedoCommand redoCommand = prepareRedoCommand(model, undoRedoStack); + Index outOfBoundIndex = Index.fromOneBased(model.getFilteredTaskList().size() + 1); + DeleteTaskCommand deleteTaskCommand = prepareCommand(outOfBoundIndex); + + // execution failed -> deleteTaskCommand not pushed into undoRedoStack + assertCommandFailure(deleteTaskCommand, model, Messages.MESSAGE_INVALID_TASK_DISPLAYED_INDEX); + + // no commands in undoRedoStack -> undoCommand and redoCommand fail + assertCommandFailure(undoCommand, model, UndoCommand.MESSAGE_FAILURE); + assertCommandFailure(redoCommand, model, RedoCommand.MESSAGE_FAILURE); + } + + /** + * 1. Deletes a {@code task} from a filtered list. + * 2. Undo the deletion. + * 3. The unfiltered list should be shown now. Verify that the index of the previously deleted task in the + * unfiltered list is different from the index at the filtered list. + * 4. Redo the deletion. This ensures {@code RedoCommand} deletes the task object regardless of indexing. + */ + @Test + public void executeUndoRedo_validIndexFilteredList_sameTaskDeleted() throws Exception { + UndoRedoStack undoRedoStack = new UndoRedoStack(); + UndoCommand undoCommand = prepareUndoCommand(model, undoRedoStack); + RedoCommand redoCommand = prepareRedoCommand(model, undoRedoStack); + DeleteTaskCommand deleteTaskCommand = prepareCommand(INDEX_FIRST_TASK); + Model expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs()); + + showTaskAtIndex(model, INDEX_SECOND_TASK); + Task taskToDelete = model.getFilteredTaskList().get(INDEX_FIRST_TASK.getZeroBased()); + // delete -> deletes second task in unfiltered task list / first task in filtered task list + deleteTaskCommand.execute(); + undoRedoStack.push(deleteTaskCommand); + + // undo -> reverts addressbook back to previous state and filtered task list to show all tasks + assertCommandSuccess(undoCommand, model, UndoCommand.MESSAGE_SUCCESS, expectedModel); + + expectedModel.deleteTask(taskToDelete); + assertNotEquals(taskToDelete, model.getFilteredTaskList().get(INDEX_FIRST_TASK.getZeroBased())); + // redo -> deletes same second task in unfiltered task list + assertCommandSuccess(redoCommand, model, RedoCommand.MESSAGE_SUCCESS, expectedModel); + } + + @Test + public void equals() throws Exception { + DeleteTaskCommand deleteFirstCommand = prepareCommand(INDEX_FIRST_TASK); + DeleteTaskCommand deleteSecondCommand = prepareCommand(INDEX_SECOND_TASK); + + // same object -> returns true + assertTrue(deleteFirstCommand.equals(deleteFirstCommand)); + + // same values -> returns true + DeleteTaskCommand deleteFirstCommandCopy = prepareCommand(INDEX_FIRST_TASK); + assertTrue(deleteFirstCommand.equals(deleteFirstCommandCopy)); + + // one command preprocessed when previously equal -> returns false + deleteFirstCommandCopy.preprocessUndoableCommand(); + assertFalse(deleteFirstCommand.equals(deleteFirstCommandCopy)); + + // different types -> returns false + assertFalse(deleteFirstCommand.equals(1)); + + // null -> returns false + assertFalse(deleteFirstCommand.equals(null)); + + // different person -> returns false + assertFalse(deleteFirstCommand.equals(deleteSecondCommand)); + } + + /** + * Returns a {@code DeleteTaskCommand} with the parameter {@code index}. + */ + private DeleteTaskCommand prepareCommand(Index index) { + DeleteTaskCommand deleteTaskCommand = new DeleteTaskCommand(index); + deleteTaskCommand.setData(model, new CommandHistory(), new UndoRedoStack()); + return deleteTaskCommand; + } + + /** + * Updates {@code model}'s filtered tasks list to show no one. + */ + private void showNoTask(Model model) { + model.updateFilteredTaskList(t -> false); + + assertTrue(model.getFilteredTaskList().isEmpty()); + } +} diff --git a/src/test/java/seedu/address/logic/commands/EditCommandTest.java b/src/test/java/seedu/address/logic/commands/EditCommandTest.java index a8b104d3a81d..af93b003b480 100644 --- a/src/test/java/seedu/address/logic/commands/EditCommandTest.java +++ b/src/test/java/seedu/address/logic/commands/EditCommandTest.java @@ -5,17 +5,24 @@ import static org.junit.Assert.assertTrue; import static seedu.address.logic.commands.CommandTestUtil.DESC_AMY; import static seedu.address.logic.commands.CommandTestUtil.DESC_BOB; +import static seedu.address.logic.commands.CommandTestUtil.TUTEE_TAG; +import static seedu.address.logic.commands.CommandTestUtil.VALID_EDUCATION_LEVEL_AMY; +import static seedu.address.logic.commands.CommandTestUtil.VALID_GRADE_AMY; 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_SCHOOL_AMY; +import static seedu.address.logic.commands.CommandTestUtil.VALID_SUBJECT_AMY; import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_HUSBAND; import static seedu.address.logic.commands.CommandTestUtil.assertCommandFailure; import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess; import static seedu.address.logic.commands.CommandTestUtil.prepareRedoCommand; import static seedu.address.logic.commands.CommandTestUtil.prepareUndoCommand; import static seedu.address.logic.commands.CommandTestUtil.showPersonAtIndex; +import static seedu.address.logic.commands.EditCommand.MESSAGE_INVALID_PERSON_TO_EDIT; import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON; import static seedu.address.testutil.TypicalIndexes.INDEX_SECOND_PERSON; -import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook; +import static seedu.address.testutil.typicaladdressbook.TypicalAddressBookCompiler.getTypicalAddressBook1; +import static seedu.address.testutil.typicaladdressbook.TypicalAddressBookCompiler.getTypicalAddressBook2; import org.junit.Test; @@ -29,15 +36,17 @@ import seedu.address.model.ModelManager; import seedu.address.model.UserPrefs; import seedu.address.model.person.Person; +import seedu.address.model.tutee.Tutee; import seedu.address.testutil.EditPersonDescriptorBuilder; import seedu.address.testutil.PersonBuilder; +import seedu.address.testutil.TuteeBuilder; /** * Contains integration tests (interaction with the Model, UndoCommand and RedoCommand) and unit tests for EditCommand. */ public class EditCommandTest { - private Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + private Model model = new ModelManager(getTypicalAddressBook1(), new UserPrefs()); @Test public void execute_allFieldsSpecifiedUnfilteredList_success() throws Exception { @@ -150,6 +159,54 @@ public void execute_invalidPersonIndexFilteredList_failure() { assertCommandFailure(editCommand, model, Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); } + //@@author ChoChihTun + @Test + public void execute_editTuteeFields_success() { + // Address book with typical tutee inside + model = new ModelManager(getTypicalAddressBook2(), new UserPrefs()); + + Tutee tuteeToEdit = new TuteeBuilder().build(); + EditPersonDescriptor descriptor = new EditPersonDescriptorBuilder(tuteeToEdit).build(); + EditCommand editCommand = prepareCommand(INDEX_FIRST_PERSON, descriptor); + + String expectedMessage = String.format(EditCommand.MESSAGE_EDIT_PERSON_SUCCESS, tuteeToEdit); + + Model expectedModel = new ModelManager(new AddressBook(model.getAddressBook()), new UserPrefs()); + + assertCommandSuccess(editCommand, model, expectedMessage, expectedModel); + } + + @Test + public void execute_invalidTagForPerson_failure() { + EditPersonDescriptor descriptor = new EditPersonDescriptorBuilder().withTags(TUTEE_TAG).build(); + EditCommand editCommand = prepareCommand(INDEX_FIRST_PERSON, descriptor); + assertCommandFailure(editCommand, model, MESSAGE_INVALID_PERSON_TO_EDIT); + } + + @Test + public void execute_invalidFieldsEditedForPerson_failure() { + // Edit subject + EditPersonDescriptor descriptor = new EditPersonDescriptorBuilder().withSubject(VALID_SUBJECT_AMY).build(); + EditCommand editCommand = prepareCommand(INDEX_FIRST_PERSON, descriptor); + assertCommandFailure(editCommand, model, MESSAGE_INVALID_PERSON_TO_EDIT); + + // Edit grade + descriptor = new EditPersonDescriptorBuilder().withGrade(VALID_GRADE_AMY).build(); + editCommand = prepareCommand(INDEX_FIRST_PERSON, descriptor); + assertCommandFailure(editCommand, model, MESSAGE_INVALID_PERSON_TO_EDIT); + + // Edit education level + descriptor = new EditPersonDescriptorBuilder().withEducationLevel(VALID_EDUCATION_LEVEL_AMY).build(); + editCommand = prepareCommand(INDEX_FIRST_PERSON, descriptor); + assertCommandFailure(editCommand, model, MESSAGE_INVALID_PERSON_TO_EDIT); + + // Edit school + descriptor = new EditPersonDescriptorBuilder().withSchool(VALID_SCHOOL_AMY).build(); + editCommand = prepareCommand(INDEX_FIRST_PERSON, descriptor); + assertCommandFailure(editCommand, model, MESSAGE_INVALID_PERSON_TO_EDIT); + } + //@@author + @Test public void executeUndoRedo_validIndexUnfilteredList_success() throws Exception { UndoRedoStack undoRedoStack = new UndoRedoStack(); diff --git a/src/test/java/seedu/address/logic/commands/FindCommandTest.java b/src/test/java/seedu/address/logic/commands/FindCommandTest.java deleted file mode 100644 index dee1f007f751..000000000000 --- a/src/test/java/seedu/address/logic/commands/FindCommandTest.java +++ /dev/null @@ -1,98 +0,0 @@ -package seedu.address.logic.commands; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; -import static seedu.address.commons.core.Messages.MESSAGE_PERSONS_LISTED_OVERVIEW; -import static seedu.address.testutil.TypicalPersons.CARL; -import static seedu.address.testutil.TypicalPersons.ELLE; -import static seedu.address.testutil.TypicalPersons.FIONA; -import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook; - -import java.util.Arrays; -import java.util.Collections; -import java.util.List; - -import org.junit.Test; - -import seedu.address.logic.CommandHistory; -import seedu.address.logic.UndoRedoStack; -import seedu.address.model.AddressBook; -import seedu.address.model.Model; -import seedu.address.model.ModelManager; -import seedu.address.model.UserPrefs; -import seedu.address.model.person.NameContainsKeywordsPredicate; -import seedu.address.model.person.Person; - -/** - * Contains integration tests (interaction with the Model) for {@code FindCommand}. - */ -public class FindCommandTest { - private Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs()); - - @Test - public void equals() { - NameContainsKeywordsPredicate firstPredicate = - new NameContainsKeywordsPredicate(Collections.singletonList("first")); - NameContainsKeywordsPredicate secondPredicate = - new NameContainsKeywordsPredicate(Collections.singletonList("second")); - - FindCommand findFirstCommand = new FindCommand(firstPredicate); - FindCommand findSecondCommand = new FindCommand(secondPredicate); - - // same object -> returns true - assertTrue(findFirstCommand.equals(findFirstCommand)); - - // same values -> returns true - FindCommand findFirstCommandCopy = new FindCommand(firstPredicate); - assertTrue(findFirstCommand.equals(findFirstCommandCopy)); - - // different types -> returns false - assertFalse(findFirstCommand.equals(1)); - - // null -> returns false - assertFalse(findFirstCommand.equals(null)); - - // different person -> returns false - assertFalse(findFirstCommand.equals(findSecondCommand)); - } - - @Test - public void execute_zeroKeywords_noPersonFound() { - String expectedMessage = String.format(MESSAGE_PERSONS_LISTED_OVERVIEW, 0); - FindCommand command = prepareCommand(" "); - assertCommandSuccess(command, expectedMessage, Collections.emptyList()); - } - - @Test - public void execute_multipleKeywords_multiplePersonsFound() { - String expectedMessage = String.format(MESSAGE_PERSONS_LISTED_OVERVIEW, 3); - FindCommand command = prepareCommand("Kurz Elle Kunz"); - assertCommandSuccess(command, expectedMessage, Arrays.asList(CARL, ELLE, FIONA)); - } - - /** - * Parses {@code userInput} into a {@code FindCommand}. - */ - private FindCommand prepareCommand(String userInput) { - FindCommand command = - new FindCommand(new NameContainsKeywordsPredicate(Arrays.asList(userInput.split("\\s+")))); - command.setData(model, new CommandHistory(), new UndoRedoStack()); - return command; - } - - /** - * Asserts that {@code command} is successfully executed, and
- * - the command feedback is equal to {@code expectedMessage}
- * - the {@code FilteredList} is equal to {@code expectedList}
- * - the {@code AddressBook} in model remains the same after executing the {@code command} - */ - private void assertCommandSuccess(FindCommand command, String expectedMessage, List expectedList) { - AddressBook expectedAddressBook = new AddressBook(model.getAddressBook()); - CommandResult commandResult = command.execute(); - - assertEquals(expectedMessage, commandResult.feedbackToUser); - assertEquals(expectedList, model.getFilteredPersonList()); - assertEquals(expectedAddressBook, model.getAddressBook()); - } -} diff --git a/src/test/java/seedu/address/logic/commands/FindPersonCommandTest.java b/src/test/java/seedu/address/logic/commands/FindPersonCommandTest.java new file mode 100644 index 000000000000..a32ff9ab5ce6 --- /dev/null +++ b/src/test/java/seedu/address/logic/commands/FindPersonCommandTest.java @@ -0,0 +1,145 @@ +package seedu.address.logic.commands; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static seedu.address.commons.core.Messages.MESSAGE_PERSONS_LISTED_OVERVIEW; +import static seedu.address.logic.commands.CommandTestUtil.VALID_EDUCATION_LEVEL_AMY; +import static seedu.address.logic.commands.CommandTestUtil.VALID_GRADE_AMY; +import static seedu.address.logic.commands.CommandTestUtil.VALID_GRADE_BOB; +import static seedu.address.logic.commands.CommandTestUtil.VALID_NAME_AMY; +import static seedu.address.logic.commands.CommandTestUtil.VALID_NAME_BOB; +import static seedu.address.logic.commands.CommandTestUtil.VALID_SCHOOL_AMY; +import static seedu.address.logic.commands.CommandTestUtil.VALID_SUBJECT_BOB; + +import static seedu.address.model.person.PersonSortUtil.CATEGORY_EDUCATION_LEVEL; +import static seedu.address.model.person.PersonSortUtil.CATEGORY_GRADE; +import static seedu.address.model.person.PersonSortUtil.CATEGORY_NAME; +import static seedu.address.model.person.PersonSortUtil.CATEGORY_SCHOOL; +import static seedu.address.model.person.PersonSortUtil.CATEGORY_SUBJECT; + +import static seedu.address.testutil.typicaladdressbook.TypicalAddressBookCompiler.getTypicalAddressBook2; +import static seedu.address.testutil.typicaladdressbook.TypicalTutees.ALICETUTEE; +import static seedu.address.testutil.typicaladdressbook.TypicalTutees.AMYTUTEE; +import static seedu.address.testutil.typicaladdressbook.TypicalTutees.BOBTUTEE; + +import java.util.Arrays; +import java.util.List; + +import org.junit.Test; + +import seedu.address.logic.CommandHistory; +import seedu.address.logic.UndoRedoStack; +import seedu.address.model.AddressBook; +import seedu.address.model.Model; +import seedu.address.model.ModelManager; +import seedu.address.model.UserPrefs; +import seedu.address.model.person.Person; + +//@@author yungyung04 +/** + * Contains integration tests (interaction with the Model) for {@code FindPersonCommand}. + */ +public class FindPersonCommandTest { + private static final int INDEX_FIRST_ELEMENT = 0; + private static final int INDEX_SECOND_ELEMENT = 1; + + private Model model = new ModelManager(getTypicalAddressBook2(), new UserPrefs()); + + private final String[] firstNameKeywords = {VALID_NAME_BOB.split("\\s+")[INDEX_FIRST_ELEMENT], + VALID_NAME_AMY.split("\\s+")[INDEX_SECOND_ELEMENT]}; + private final String[] secondNameKeywords = {VALID_NAME_BOB.split("\\s+")[INDEX_FIRST_ELEMENT]}; + + private final FindPersonCommand findFirstName = new FindPersonCommand(CATEGORY_NAME, firstNameKeywords); + private final FindPersonCommand findSecondName = new FindPersonCommand(CATEGORY_NAME, secondNameKeywords); + + @Test + public void equals() { + // same object -> returns true + assertTrue(findFirstName.equals(findFirstName)); + + // same values -> returns true + FindPersonCommand findFirstCommandCopy = new FindPersonCommand(CATEGORY_NAME, firstNameKeywords); + assertTrue(findFirstName.equals(findFirstCommandCopy)); + + // different types -> returns false + assertFalse(findFirstName.equals(1)); + + // null -> returns false + assertFalse(findFirstName.equals(null)); + + // different person -> returns false + assertFalse(findFirstName.equals(findSecondName)); + } + + @Test + public void execute_findName_foundSuccessfully() { + //multiple keywords + findFirstName.setData(model, new CommandHistory(), new UndoRedoStack()); + String expectedMessage = String.format(FindPersonCommand.MESSAGE_SUCCESS + "\n" + + MESSAGE_PERSONS_LISTED_OVERVIEW, 2); + assertCommandSuccess(findFirstName, expectedMessage, Arrays.asList(AMYTUTEE, BOBTUTEE)); + + //single keyword + findSecondName.setData(model, new CommandHistory(), new UndoRedoStack()); + expectedMessage = String.format(FindPersonCommand.MESSAGE_SUCCESS + "\n" + MESSAGE_PERSONS_LISTED_OVERVIEW, 1); + assertCommandSuccess(findSecondName, expectedMessage, Arrays.asList(BOBTUTEE)); + } + + @Test + public void execute_findEducatonLevel_foundSuccessfully() { + String[] educationLevelKeywords = {VALID_EDUCATION_LEVEL_AMY}; + FindPersonCommand findEducationLevel = new FindPersonCommand(CATEGORY_EDUCATION_LEVEL, educationLevelKeywords); + findEducationLevel.setData(model, new CommandHistory(), new UndoRedoStack()); + String expectedMessage = String.format(FindPersonCommand.MESSAGE_SUCCESS + "\n" + + MESSAGE_PERSONS_LISTED_OVERVIEW, 2); + //Alice and Amy have the same education level + assertCommandSuccess(findEducationLevel, expectedMessage, Arrays.asList(ALICETUTEE, AMYTUTEE)); + } + + @Test + public void execute_findGrade_foundSuccessfully() { + String[] gradeKeywords = {VALID_GRADE_AMY, VALID_GRADE_BOB}; + FindPersonCommand findGrade = new FindPersonCommand(CATEGORY_GRADE, gradeKeywords); + findGrade.setData(model, new CommandHistory(), new UndoRedoStack()); + String expectedMessage = String.format(FindPersonCommand.MESSAGE_SUCCESS + "\n" + + MESSAGE_PERSONS_LISTED_OVERVIEW, 2); + assertCommandSuccess(findGrade, expectedMessage, Arrays.asList(AMYTUTEE, BOBTUTEE)); + } + + @Test + public void execute_findSchool_foundSuccessfully() { + String[] schoolKeywords = {VALID_SCHOOL_AMY.split("\\s+")[INDEX_FIRST_ELEMENT]}; + FindPersonCommand findSchool = new FindPersonCommand(CATEGORY_SCHOOL, schoolKeywords); + findSchool.setData(model, new CommandHistory(), new UndoRedoStack()); + String expectedMessage = String.format(FindPersonCommand.MESSAGE_SUCCESS + "\n" + + MESSAGE_PERSONS_LISTED_OVERVIEW, 1); + assertCommandSuccess(findSchool, expectedMessage, Arrays.asList(AMYTUTEE)); + } + + @Test + public void execute_findSubject_foundSuccessfully() { + String[] subjectKeywords = {VALID_SUBJECT_BOB}; + FindPersonCommand findSubject = new FindPersonCommand(CATEGORY_SUBJECT, subjectKeywords); + findSubject.setData(model, new CommandHistory(), new UndoRedoStack()); + String expectedMessage = String.format(FindPersonCommand.MESSAGE_SUCCESS + "\n" + + MESSAGE_PERSONS_LISTED_OVERVIEW, 2); + //Alice and Bob learn the same subject. + assertCommandSuccess(findSubject, expectedMessage, Arrays.asList(ALICETUTEE, BOBTUTEE)); + } + + /** + * Asserts that {@code command} is successfully executed, and
+ * - the command feedback is equal to {@code expectedMessage}
+ * - the {@code FilteredList} is equal to {@code expectedList}
+ * - the {@code AddressBook} in model remains the same after executing the {@code command} + */ + private void assertCommandSuccess(FindPersonCommand command, String expectedMessage, List expectedList) { + AddressBook expectedAddressBook = new AddressBook(model.getAddressBook()); + CommandResult commandResult = command.execute(); + + assertEquals(expectedMessage, commandResult.feedbackToUser); + assertEquals(expectedList, model.getFilteredPersonList()); + assertEquals(expectedAddressBook, model.getAddressBook()); + } +} diff --git a/src/test/java/seedu/address/logic/commands/FindTaskCommandTest.java b/src/test/java/seedu/address/logic/commands/FindTaskCommandTest.java new file mode 100644 index 000000000000..42a1ed0d265d --- /dev/null +++ b/src/test/java/seedu/address/logic/commands/FindTaskCommandTest.java @@ -0,0 +1,97 @@ +package seedu.address.logic.commands; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import static seedu.address.commons.core.Messages.MESSAGE_TASKS_LISTED_OVERVIEW; +import static seedu.address.model.task.TaskSortUtil.CATEGORY_MONTH; +import static seedu.address.testutil.typicaladdressbook.TypicalAddressBookCompiler.getTypicalAddressBook2; +import static seedu.address.testutil.typicaladdressbook.TypicalTasks.TASK_ALICE; +import static seedu.address.testutil.typicaladdressbook.TypicalTasks.TASK_BENSON; +import static seedu.address.testutil.typicaladdressbook.TypicalTasks.TASK_CARL; + +import java.util.Arrays; +import java.util.List; + +import org.junit.Test; + +import seedu.address.logic.CommandHistory; +import seedu.address.logic.UndoRedoStack; +import seedu.address.model.AddressBook; +import seedu.address.model.Model; +import seedu.address.model.ModelManager; +import seedu.address.model.Task; +import seedu.address.model.UserPrefs; + +//@@author ChoChihTun +/** + * Contains integration tests (interaction with the Model) for {@code FindTaskCommand}. + */ +public class FindTaskCommandTest { + private static final String OCTOBER = "10"; + private static final String NOVEMBER = "11"; + private static final String DECEMBER = "12"; + + private final String[] monthBetweenKeywords = {OCTOBER, NOVEMBER, DECEMBER}; + private final String[] monthNamelyKeyword = {OCTOBER}; + + private final FindTaskCommand findMonthBetweenKeywordsCommand = + new FindTaskCommand(CATEGORY_MONTH, monthBetweenKeywords); + private final FindTaskCommand findMonthNamelyKeywordCommand = + new FindTaskCommand(CATEGORY_MONTH, monthNamelyKeyword); + + private Model model = new ModelManager(getTypicalAddressBook2(), new UserPrefs()); + + @Test + public void equals_validArgs_returnsTrue() { + // same object + assertTrue(findMonthBetweenKeywordsCommand.equals(findMonthBetweenKeywordsCommand)); + + // objects with same value + FindTaskCommand findMonthBetweenKeywordsCommandCopy = new FindTaskCommand(CATEGORY_MONTH, monthBetweenKeywords); + assertTrue(findMonthBetweenKeywordsCommand.equals(findMonthBetweenKeywordsCommandCopy)); + } + + @Test + public void equals_invalidArgs_returnsFalse() { + // null + assertFalse(findMonthBetweenKeywordsCommand.equals(null)); + + // wrong parameter data type + assertFalse(findMonthBetweenKeywordsCommand.equals(1)); + + // different commands + assertFalse(findMonthBetweenKeywordsCommand.equals(findMonthNamelyKeywordCommand)); + } + + @Test + public void execute_findMonth_success() { + // between months + findMonthBetweenKeywordsCommand.setData(model, new CommandHistory(), new UndoRedoStack()); + String expectedMessage = String.format(FindTaskCommand.MESSAGE_SUCCESS + "\n" + + MESSAGE_TASKS_LISTED_OVERVIEW, 3); + assertCommandSuccess(findMonthBetweenKeywordsCommand, expectedMessage, + Arrays.asList(TASK_ALICE, TASK_BENSON, TASK_CARL)); + + // namely month + findMonthNamelyKeywordCommand.setData(model, new CommandHistory(), new UndoRedoStack()); + expectedMessage = String.format(FindTaskCommand.MESSAGE_SUCCESS + "\n" + MESSAGE_TASKS_LISTED_OVERVIEW, 2); + assertCommandSuccess(findMonthNamelyKeywordCommand, expectedMessage, Arrays.asList(TASK_ALICE, TASK_BENSON)); + } + + /** + * Asserts that {@code command} is successfully executed, and
+ * - the command feedback is equal to {@code expectedMessage}
+ * - the {@code FilteredList} is equal to {@code expectedList}
+ * - the {@code AddressBook} in model remains the same after executing the {@code command} + */ + private void assertCommandSuccess(FindTaskCommand command, String expectedMessage, List expectedList) { + AddressBook expectedAddressBook = new AddressBook(model.getAddressBook()); + CommandResult commandResult = command.execute(); + + assertEquals(expectedMessage, commandResult.feedbackToUser); + assertEquals(expectedList, model.getFilteredTaskList()); + assertEquals(expectedAddressBook, model.getAddressBook()); + } +} diff --git a/src/test/java/seedu/address/logic/commands/ListCommandTest.java b/src/test/java/seedu/address/logic/commands/ListCommandTest.java index 4ee519e3668e..d1ed769a4af6 100644 --- a/src/test/java/seedu/address/logic/commands/ListCommandTest.java +++ b/src/test/java/seedu/address/logic/commands/ListCommandTest.java @@ -3,7 +3,7 @@ import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess; import static seedu.address.logic.commands.CommandTestUtil.showPersonAtIndex; import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON; -import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook; +import static seedu.address.testutil.typicaladdressbook.TypicalAddressBookCompiler.getTypicalAddressBook1; import org.junit.Before; import org.junit.Test; @@ -25,7 +25,7 @@ public class ListCommandTest { @Before public void setUp() { - model = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + model = new ModelManager(getTypicalAddressBook1(), new UserPrefs()); expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs()); listCommand = new ListCommand(); diff --git a/src/test/java/seedu/address/logic/commands/ListTaskCommandTest.java b/src/test/java/seedu/address/logic/commands/ListTaskCommandTest.java new file mode 100644 index 000000000000..2444d3f7a1b8 --- /dev/null +++ b/src/test/java/seedu/address/logic/commands/ListTaskCommandTest.java @@ -0,0 +1,47 @@ +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_PERSON; +import static seedu.address.testutil.TypicalTasks.getTypicalAddressBook; + +import org.junit.Before; +import org.junit.Test; + +import seedu.address.logic.CommandHistory; +import seedu.address.logic.UndoRedoStack; +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. + */ +//@@author a-shakra +public class ListTaskCommandTest { + + private Model model; + private Model expectedModel; + private ListTaskCommand listTaskCommand; + + @Before + public void setUp() { + + model = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs()); + + listTaskCommand = new ListTaskCommand(); + listTaskCommand.setData(model, new CommandHistory(), new UndoRedoStack()); + } + + @Test + public void execute_listIsNotFiltered_showsSameList() { + assertCommandSuccess(listTaskCommand, model, ListTaskCommand.MESSAGE_SUCCESS, expectedModel); + } + + @Test + public void execute_listIsFiltered_showsEverything() { + showTaskAtIndex(model, INDEX_FIRST_PERSON); + assertCommandSuccess(listTaskCommand, model, ListTaskCommand.MESSAGE_SUCCESS, expectedModel); + } +} diff --git a/src/test/java/seedu/address/logic/commands/ListTuteeCommandTest.java b/src/test/java/seedu/address/logic/commands/ListTuteeCommandTest.java new file mode 100644 index 000000000000..fae29af08e16 --- /dev/null +++ b/src/test/java/seedu/address/logic/commands/ListTuteeCommandTest.java @@ -0,0 +1,56 @@ +package seedu.address.logic.commands; + +import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess; +import static seedu.address.logic.commands.CommandTestUtil.showPersonAtIndex; +import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON; +import static seedu.address.testutil.typicaladdressbook.TypicalAddressBookCompiler.getTypicalAddressBook2; + +import org.junit.Before; +import org.junit.Test; + +import seedu.address.logic.CommandHistory; +import seedu.address.logic.UndoRedoStack; +import seedu.address.model.Model; +import seedu.address.model.ModelManager; +import seedu.address.model.UserPrefs; + +//@@author yungyung04 +/** + * Contains integration tests (interaction with the Model) and unit tests for ListTuteeCommand. + */ +public class ListTuteeCommandTest { + + private Model model; + private Model expectedModel; + private ListTuteeCommand listTuteeCommand; + + @Before + public void setUp() { + model = new ModelManager(getTypicalAddressBook2(), new UserPrefs()); + expectedModel = setExpectedModel(model); + + listTuteeCommand = new ListTuteeCommand(); + listTuteeCommand.setData(model, new CommandHistory(), new UndoRedoStack()); + } + + @Test + public void execute_tuteeListIsNotFiltered_showsSameList() { + assertCommandSuccess(listTuteeCommand, model, ListTuteeCommand.MESSAGE_SUCCESS, expectedModel); + } + + @Test + public void execute_tuteeListIsFiltered_showsEverything() { + showPersonAtIndex(model, INDEX_FIRST_PERSON); + assertCommandSuccess(listTuteeCommand, model, ListTuteeCommand.MESSAGE_SUCCESS, expectedModel); + } + + /** + * Returns a model that has been filtered to show only tutees + */ + private ModelManager setExpectedModel(Model model) { + ModelManager modelManager = new ModelManager(model.getAddressBook(), new UserPrefs()); + modelManager.updateFilteredPersonList(Model.PREDICATE_SHOW_ALL_TUTEES); + return modelManager; + } + +} diff --git a/src/test/java/seedu/address/logic/commands/RedoCommandTest.java b/src/test/java/seedu/address/logic/commands/RedoCommandTest.java index e615f089a4f2..c5d8f1bae2e4 100644 --- a/src/test/java/seedu/address/logic/commands/RedoCommandTest.java +++ b/src/test/java/seedu/address/logic/commands/RedoCommandTest.java @@ -6,7 +6,7 @@ import static seedu.address.logic.commands.CommandTestUtil.deleteFirstPerson; import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON; import static seedu.address.testutil.TypicalIndexes.INDEX_SECOND_PERSON; -import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook; +import static seedu.address.testutil.typicaladdressbook.TypicalAddressBookCompiler.getTypicalAddressBook1; import java.util.Arrays; import java.util.Collections; @@ -24,7 +24,7 @@ public class RedoCommandTest { private static final CommandHistory EMPTY_COMMAND_HISTORY = new CommandHistory(); private static final UndoRedoStack EMPTY_STACK = new UndoRedoStack(); - private final Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + private final Model model = new ModelManager(getTypicalAddressBook1(), new UserPrefs()); private final DeleteCommand deleteCommandOne = new DeleteCommand(INDEX_FIRST_PERSON); private final DeleteCommand deleteCommandTwo = new DeleteCommand(INDEX_SECOND_PERSON); @@ -42,7 +42,7 @@ public void execute() { Collections.emptyList(), Arrays.asList(deleteCommandTwo, deleteCommandOne)); RedoCommand redoCommand = new RedoCommand(); redoCommand.setData(model, EMPTY_COMMAND_HISTORY, undoRedoStack); - Model expectedModel = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + Model expectedModel = new ModelManager(getTypicalAddressBook1(), new UserPrefs()); // multiple commands in redoStack deleteFirstPerson(expectedModel); diff --git a/src/test/java/seedu/address/logic/commands/SelectCommandTest.java b/src/test/java/seedu/address/logic/commands/SelectCommandTest.java index 4840900602ac..7d68f17299c4 100644 --- a/src/test/java/seedu/address/logic/commands/SelectCommandTest.java +++ b/src/test/java/seedu/address/logic/commands/SelectCommandTest.java @@ -8,7 +8,7 @@ import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON; import static seedu.address.testutil.TypicalIndexes.INDEX_SECOND_PERSON; import static seedu.address.testutil.TypicalIndexes.INDEX_THIRD_PERSON; -import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook; +import static seedu.address.testutil.typicaladdressbook.TypicalAddressBookCompiler.getTypicalAddressBook1; import org.junit.Before; import org.junit.Rule; @@ -36,7 +36,7 @@ public class SelectCommandTest { @Before public void setUp() { - model = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + model = new ModelManager(getTypicalAddressBook1(), new UserPrefs()); } @Test diff --git a/src/test/java/seedu/address/logic/commands/SortPersonCommandTest.java b/src/test/java/seedu/address/logic/commands/SortPersonCommandTest.java new file mode 100644 index 000000000000..285c0d4e743d --- /dev/null +++ b/src/test/java/seedu/address/logic/commands/SortPersonCommandTest.java @@ -0,0 +1,118 @@ +package seedu.address.logic.commands; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static seedu.address.model.person.PersonSortUtil.CATEGORY_EDUCATION_LEVEL; +import static seedu.address.model.person.PersonSortUtil.CATEGORY_GRADE; +import static seedu.address.model.person.PersonSortUtil.CATEGORY_NAME; +import static seedu.address.model.person.PersonSortUtil.CATEGORY_SCHOOL; +import static seedu.address.model.person.PersonSortUtil.CATEGORY_SUBJECT; +import static seedu.address.testutil.typicaladdressbook.TypicalAddressBookCompiler.getTypicalAddressBook2; +import static seedu.address.testutil.typicaladdressbook.TypicalPersons.DANIEL; +import static seedu.address.testutil.typicaladdressbook.TypicalTutees.ALICETUTEE; +import static seedu.address.testutil.typicaladdressbook.TypicalTutees.AMYTUTEE; +import static seedu.address.testutil.typicaladdressbook.TypicalTutees.BOBTUTEE; + +import java.util.Arrays; +import java.util.List; + +import org.junit.Test; + +import seedu.address.logic.CommandHistory; +import seedu.address.logic.UndoRedoStack; +import seedu.address.model.AddressBook; +import seedu.address.model.Model; +import seedu.address.model.ModelManager; +import seedu.address.model.UserPrefs; +import seedu.address.model.person.Person; + +//@@author yungyung04 +/** + * Contains integration tests (interaction with the Model) for {@code SortPersonCommand}. + */ +public class SortPersonCommandTest { + + private Model model = new ModelManager(getTypicalAddressBook2(), new UserPrefs()); + + private final SortPersonCommand sortName = new SortPersonCommand(CATEGORY_NAME); + + @Test + public void equals() { + // same object -> returns true + assertTrue(sortName.equals(sortName)); + + // same values -> returns true + SortPersonCommand sortNameCopy = new SortPersonCommand(CATEGORY_NAME); + assertTrue(sortName.equals(sortNameCopy)); + + // different types -> returns false + assertFalse(sortName.equals(1)); + + // null -> returns false + assertFalse(sortName.equals(null)); + + // different category -> returns false + SortPersonCommand sortGrade = new SortPersonCommand(CATEGORY_GRADE); + assertFalse(sortName.equals(sortGrade)); + } + + @Test + public void execute_sortName_sortedSuccessfully() { + sortName.setData(model, new CommandHistory(), new UndoRedoStack()); + String expectedMessage = String.format(SortPersonCommand.MESSAGE_SUCCESS); + assertCommandSuccess(sortName, expectedMessage, + Arrays.asList(ALICETUTEE, AMYTUTEE, BOBTUTEE, DANIEL)); + } + + @Test + public void execute_sortEducatonLevel_sortedSuccessfully() { + SortPersonCommand sortEducationLevel = new SortPersonCommand(CATEGORY_EDUCATION_LEVEL); + sortEducationLevel.setData(model, new CommandHistory(), new UndoRedoStack()); + String expectedMessage = String.format(SortPersonCommand.MESSAGE_SUCCESS); + assertCommandSuccess(sortEducationLevel, expectedMessage, + Arrays.asList(BOBTUTEE, ALICETUTEE, AMYTUTEE, DANIEL)); + } + + @Test + public void execute_sortGrade_sortedSuccessfully() { + SortPersonCommand sortGrade = new SortPersonCommand(CATEGORY_GRADE); + sortGrade.setData(model, new CommandHistory(), new UndoRedoStack()); + String expectedMessage = String.format(SortPersonCommand.MESSAGE_SUCCESS); + assertCommandSuccess(sortGrade, expectedMessage, + Arrays.asList(BOBTUTEE, AMYTUTEE, ALICETUTEE, DANIEL)); + } + + @Test + public void execute_sortSchool_sortedSuccessfully() { + SortPersonCommand sortSchool = new SortPersonCommand(CATEGORY_SCHOOL); + sortSchool.setData(model, new CommandHistory(), new UndoRedoStack()); + String expectedMessage = String.format(SortPersonCommand.MESSAGE_SUCCESS); + assertCommandSuccess(sortSchool, expectedMessage, + Arrays.asList(ALICETUTEE, AMYTUTEE, BOBTUTEE, DANIEL)); + } + + @Test + public void execute_sortSubject_sortedSuccessfully() { + SortPersonCommand sortSubject = new SortPersonCommand(CATEGORY_SUBJECT); + sortSubject.setData(model, new CommandHistory(), new UndoRedoStack()); + String expectedMessage = String.format(SortPersonCommand.MESSAGE_SUCCESS); + assertCommandSuccess(sortSubject, expectedMessage, + Arrays.asList(AMYTUTEE, ALICETUTEE, BOBTUTEE, DANIEL)); + } + + /** + * Asserts that {@code command} is successfully executed, and
+ * - the command feedback is equal to {@code expectedMessage}
+ * - the {@code FilteredList} is equal to {@code expectedList}
+ * - the {@code AddressBook} in model remains the same after executing the {@code command} + */ + private void assertCommandSuccess(SortPersonCommand command, String expectedMessage, List expectedList) { + AddressBook expectedAddressBook = new AddressBook(model.getAddressBook()); + CommandResult commandResult = command.execute(); + + assertEquals(expectedMessage, commandResult.feedbackToUser); + assertEquals(expectedList, model.getFilteredPersonList()); + assertEquals(expectedAddressBook, model.getAddressBook()); + } +} diff --git a/src/test/java/seedu/address/logic/commands/UndoCommandTest.java b/src/test/java/seedu/address/logic/commands/UndoCommandTest.java index 3eb5f2f18346..586480e8c275 100644 --- a/src/test/java/seedu/address/logic/commands/UndoCommandTest.java +++ b/src/test/java/seedu/address/logic/commands/UndoCommandTest.java @@ -5,7 +5,7 @@ import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess; import static seedu.address.logic.commands.CommandTestUtil.deleteFirstPerson; import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON; -import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook; +import static seedu.address.testutil.typicaladdressbook.TypicalAddressBookCompiler.getTypicalAddressBook1; import java.util.Arrays; import java.util.Collections; @@ -23,7 +23,7 @@ public class UndoCommandTest { private static final CommandHistory EMPTY_COMMAND_HISTORY = new CommandHistory(); private static final UndoRedoStack EMPTY_STACK = new UndoRedoStack(); - private final Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + private final Model model = new ModelManager(getTypicalAddressBook1(), new UserPrefs()); private final DeleteCommand deleteCommandOne = new DeleteCommand(INDEX_FIRST_PERSON); private final DeleteCommand deleteCommandTwo = new DeleteCommand(INDEX_FIRST_PERSON); @@ -43,12 +43,12 @@ public void execute() throws Exception { deleteCommandTwo.execute(); // multiple commands in undoStack - Model expectedModel = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + Model expectedModel = new ModelManager(getTypicalAddressBook1(), new UserPrefs()); deleteFirstPerson(expectedModel); assertCommandSuccess(undoCommand, model, UndoCommand.MESSAGE_SUCCESS, expectedModel); // single command in undoStack - expectedModel = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + expectedModel = new ModelManager(getTypicalAddressBook1(), new UserPrefs()); assertCommandSuccess(undoCommand, model, UndoCommand.MESSAGE_SUCCESS, expectedModel); // no command in undoStack diff --git a/src/test/java/seedu/address/logic/commands/UndoableCommandTest.java b/src/test/java/seedu/address/logic/commands/UndoableCommandTest.java index 7d00a7471b86..96f010c392eb 100644 --- a/src/test/java/seedu/address/logic/commands/UndoableCommandTest.java +++ b/src/test/java/seedu/address/logic/commands/UndoableCommandTest.java @@ -5,7 +5,7 @@ import static seedu.address.logic.commands.CommandTestUtil.deleteFirstPerson; import static seedu.address.logic.commands.CommandTestUtil.showPersonAtIndex; import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON; -import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook; +import static seedu.address.testutil.typicaladdressbook.TypicalAddressBookCompiler.getTypicalAddressBook1; import org.junit.Test; @@ -17,10 +17,10 @@ import seedu.address.model.person.exceptions.PersonNotFoundException; public class UndoableCommandTest { - private final Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + private final Model model = new ModelManager(getTypicalAddressBook1(), new UserPrefs()); private final DummyCommand dummyCommand = new DummyCommand(model); - private Model expectedModel = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + private Model expectedModel = new ModelManager(getTypicalAddressBook1(), new UserPrefs()); @Test public void executeUndo() throws Exception { @@ -32,7 +32,7 @@ public void executeUndo() throws Exception { // undo() should cause the model's filtered list to show all persons dummyCommand.undo(); - expectedModel = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + expectedModel = new ModelManager(getTypicalAddressBook1(), new UserPrefs()); assertEquals(expectedModel, model); } diff --git a/src/test/java/seedu/address/logic/parser/AddCommandParserTest.java b/src/test/java/seedu/address/logic/parser/AddCommandParserTest.java index c9a350c09657..536b19288d1d 100644 --- a/src/test/java/seedu/address/logic/parser/AddCommandParserTest.java +++ b/src/test/java/seedu/address/logic/parser/AddCommandParserTest.java @@ -10,6 +10,7 @@ import static seedu.address.logic.commands.CommandTestUtil.INVALID_NAME_DESC; import static seedu.address.logic.commands.CommandTestUtil.INVALID_PHONE_DESC; import static seedu.address.logic.commands.CommandTestUtil.INVALID_TAG_DESC; +import static seedu.address.logic.commands.CommandTestUtil.INVALID_TUTEE_TAG_DESC; import static seedu.address.logic.commands.CommandTestUtil.NAME_DESC_AMY; import static seedu.address.logic.commands.CommandTestUtil.NAME_DESC_BOB; import static seedu.address.logic.commands.CommandTestUtil.PHONE_DESC_AMY; @@ -18,6 +19,7 @@ import static seedu.address.logic.commands.CommandTestUtil.PREAMBLE_WHITESPACE; import static seedu.address.logic.commands.CommandTestUtil.TAG_DESC_FRIEND; import static seedu.address.logic.commands.CommandTestUtil.TAG_DESC_HUSBAND; +import static seedu.address.logic.commands.CommandTestUtil.TUTEE_TAG; import static seedu.address.logic.commands.CommandTestUtil.VALID_ADDRESS_AMY; import static seedu.address.logic.commands.CommandTestUtil.VALID_ADDRESS_BOB; import static seedu.address.logic.commands.CommandTestUtil.VALID_EMAIL_AMY; @@ -132,7 +134,11 @@ public void parse_invalidValue_failure() { // invalid tag assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + ADDRESS_DESC_BOB - + INVALID_TAG_DESC + VALID_TAG_FRIEND, Tag.MESSAGE_TAG_CONSTRAINTS); + + INVALID_TAG_DESC, Tag.MESSAGE_TAG_CONSTRAINTS); + + // invalid tutee tag + assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + ADDRESS_DESC_BOB + + INVALID_TUTEE_TAG_DESC, String.format(ParserUtil.MESSAGE_INVALID_TAG, TUTEE_TAG)); // two invalid values, only first invalid value reported assertParseFailure(parser, INVALID_NAME_DESC + PHONE_DESC_BOB + EMAIL_DESC_BOB + INVALID_ADDRESS_DESC, diff --git a/src/test/java/seedu/address/logic/parser/AddPersonalTaskCommandParserTest.java b/src/test/java/seedu/address/logic/parser/AddPersonalTaskCommandParserTest.java new file mode 100644 index 000000000000..b3400865612b --- /dev/null +++ b/src/test/java/seedu/address/logic/parser/AddPersonalTaskCommandParserTest.java @@ -0,0 +1,106 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_DATE_TIME; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_DURATION; +import static seedu.address.logic.commands.CommandTestUtil.VALID_DATE_TIME_AMY; +import static seedu.address.logic.commands.CommandTestUtil.VALID_DURATION_AMY; +import static seedu.address.logic.commands.CommandTestUtil.VALID_EMPTY_TASK_DESC; +import static seedu.address.logic.commands.CommandTestUtil.VALID_TASK_DESC_AMY; +import static seedu.address.logic.commands.CommandTestUtil.VALID_TASK_WITHOUT_DESC_AMY; +import static seedu.address.logic.commands.CommandTestUtil.VALID_TASK_WITH_DESC_AMY; +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure; +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.time.format.ResolverStyle; + +import org.junit.Test; + +import seedu.address.logic.commands.AddPersonalTaskCommand; +import seedu.address.model.personal.PersonalTask; + +//@@author ChoChihTun +public class AddPersonalTaskCommandParserTest { + private AddPersonalTaskCommandParser parser = new AddPersonalTaskCommandParser(); + + @Test + public void parse_invalidArgs_throwsParseException() { + // Invalid format + assertParseFailure(parser, "a", + String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddPersonalTaskCommand.MESSAGE_USAGE)); + assertParseFailure(parser, "1 11/01/2018 11:11 1h30m Outing", + String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddPersonalTaskCommand.MESSAGE_USAGE)); + assertParseFailure(parser, "11:11 1h30m Outing with friends", + String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddPersonalTaskCommand.MESSAGE_USAGE)); + assertParseFailure(parser, "11/01/2018 1h30m Outing with friends", + String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddPersonalTaskCommand.MESSAGE_USAGE)); + assertParseFailure(parser, "11/01/2018 11:11 Outing with friends", + String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddPersonalTaskCommand.MESSAGE_USAGE)); + assertParseFailure(parser, "11/01/2018 11:11 1h Outing with friends", + String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddPersonalTaskCommand.MESSAGE_USAGE)); + assertParseFailure(parser, "11/01/2018 11:11 30m Outing with friends", + String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddPersonalTaskCommand.MESSAGE_USAGE)); + assertParseFailure(parser, "11:11 32/01/2018 1h30m Outing with friends", + String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddPersonalTaskCommand.MESSAGE_USAGE)); + assertParseFailure(parser, "aa/01/2018 11:11 1h30m Outing with friends", + String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddPersonalTaskCommand.MESSAGE_USAGE)); + assertParseFailure(parser, "32/01/2018 11:aa 1h30m Outing with friends", + String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddPersonalTaskCommand.MESSAGE_USAGE)); + assertParseFailure(parser, "32/01/2018 11:11 1haam Outing with friends", + String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddPersonalTaskCommand.MESSAGE_USAGE)); + + // Invalid date + assertParseFailure(parser, "29/02/2018 11:11 1h30m Outing with friends", MESSAGE_INVALID_DATE_TIME); + assertParseFailure(parser, "31/04/2018 11:11 1h30m Outing with friends", MESSAGE_INVALID_DATE_TIME); + assertParseFailure(parser, "32/01/2018 11:11 1h30m Outing with friends", MESSAGE_INVALID_DATE_TIME); + + // Invalid time + assertParseFailure(parser, "11/01/2018 24:00 1h30m Outing with friends", MESSAGE_INVALID_DATE_TIME); + assertParseFailure(parser, "11/01/2018 11:60 1h30m Outing with friends", MESSAGE_INVALID_DATE_TIME); + + // Invalid duration + assertParseFailure(parser, "11/01/2018 11:11 1h60m Outing with friends", MESSAGE_INVALID_DURATION); + assertParseFailure(parser, "11/01/2018 11:11 24h0m Outing with friends", MESSAGE_INVALID_DURATION); + } + + @Test + public void parse_validArgs_success() { + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd/MM/uuuu HH:mm") + .withResolverStyle(ResolverStyle.STRICT); + + // With description + LocalDateTime taskDateTime = LocalDateTime.parse(VALID_DATE_TIME_AMY, formatter); + PersonalTask personalTask = new PersonalTask(taskDateTime, VALID_DURATION_AMY, VALID_TASK_DESC_AMY); + assertParseSuccess(parser, VALID_TASK_WITH_DESC_AMY, + new AddPersonalTaskCommand(personalTask)); + + // Without description + personalTask = new PersonalTask(taskDateTime, VALID_DURATION_AMY, VALID_EMPTY_TASK_DESC); + assertParseSuccess(parser, VALID_TASK_WITHOUT_DESC_AMY, + new AddPersonalTaskCommand(personalTask)); + + // Valid date + personalTask = new PersonalTask(LocalDateTime.parse("29/02/2016 11:20", formatter), + VALID_DURATION_AMY, VALID_EMPTY_TASK_DESC); + assertParseSuccess(parser, "29/02/2016 11:20 1h11m", + new AddPersonalTaskCommand(personalTask)); + + personalTask = new PersonalTask(LocalDateTime.parse("30/04/2016 11:20", formatter), + VALID_DURATION_AMY, VALID_EMPTY_TASK_DESC); + assertParseSuccess(parser, "30/04/2016 11:20 " + VALID_DURATION_AMY, + new AddPersonalTaskCommand(personalTask)); + + personalTask = new PersonalTask(LocalDateTime.parse("31/01/2016 11:20", formatter), + VALID_DURATION_AMY, VALID_EMPTY_TASK_DESC); + assertParseSuccess(parser, "31/01/2016 11:20 " + VALID_DURATION_AMY, + new AddPersonalTaskCommand(personalTask)); + + // Valid Time + personalTask = new PersonalTask(LocalDateTime.parse("11/01/2018 00:00", formatter), + VALID_DURATION_AMY, VALID_EMPTY_TASK_DESC); + assertParseSuccess(parser, "11/01/2018 00:00 " + VALID_DURATION_AMY, + new AddPersonalTaskCommand(personalTask)); + } +} diff --git a/src/test/java/seedu/address/logic/parser/AddTuitionTaskCommandParserTest.java b/src/test/java/seedu/address/logic/parser/AddTuitionTaskCommandParserTest.java new file mode 100644 index 000000000000..51ef69cec863 --- /dev/null +++ b/src/test/java/seedu/address/logic/parser/AddTuitionTaskCommandParserTest.java @@ -0,0 +1,109 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_DATE_TIME; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_DURATION; +import static seedu.address.logic.commands.CommandTestUtil.VALID_DATE_TIME_AMY; +import static seedu.address.logic.commands.CommandTestUtil.VALID_DURATION_AMY; +import static seedu.address.logic.commands.CommandTestUtil.VALID_EMPTY_TASK_DESC; +import static seedu.address.logic.commands.CommandTestUtil.VALID_TASK_DESC_AMY; +import static seedu.address.logic.commands.CommandTestUtil.VALID_TASK_WITHOUT_DESC_AMY; +import static seedu.address.logic.commands.CommandTestUtil.VALID_TASK_WITH_DESC_AMY; +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure; +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess; +import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON; + +import java.time.LocalDateTime; + +import java.time.format.DateTimeFormatter; +import java.time.format.ResolverStyle; + +import org.junit.Test; + +import seedu.address.logic.commands.AddTuitionTaskCommand; + +//@@author ChoChihTun +public class AddTuitionTaskCommandParserTest { + private AddTuitionTaskCommandParser parser = new AddTuitionTaskCommandParser(); + + @Test + public void parse_invalidArgs_throwsParseException() { + // Invalid format + assertParseFailure(parser, "1", + String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddTuitionTaskCommand.MESSAGE_USAGE)); + assertParseFailure(parser, "11/01/2018 11:11 1h30m tuition homework", + String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddTuitionTaskCommand.MESSAGE_USAGE)); + assertParseFailure(parser, "1 11:11 1h30m tuition homework", + String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddTuitionTaskCommand.MESSAGE_USAGE)); + assertParseFailure(parser, "1 11/01/2018 1h30m tuition homework", + String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddTuitionTaskCommand.MESSAGE_USAGE)); + assertParseFailure(parser, "1 11/01/2018 11:11 tuition homework", + String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddTuitionTaskCommand.MESSAGE_USAGE)); + assertParseFailure(parser, "1 11/01/2018 11:11 1h tuition homework", + String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddTuitionTaskCommand.MESSAGE_USAGE)); + assertParseFailure(parser, "1 11/01/2018 11:11 30m tuition homework", + String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddTuitionTaskCommand.MESSAGE_USAGE)); + assertParseFailure(parser, "1 11:11 32/01/2018 1h30m tuition homework", + String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddTuitionTaskCommand.MESSAGE_USAGE)); + assertParseFailure(parser, "aaa 32/01/2018 11:11 1h30m tuition homework", + String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddTuitionTaskCommand.MESSAGE_USAGE)); + assertParseFailure(parser, "1 aa/01/2018 11:11 1h30m tuition homework", + String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddTuitionTaskCommand.MESSAGE_USAGE)); + assertParseFailure(parser, "1 32/01/2018 11:aa 1h30m tuition homework", + String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddTuitionTaskCommand.MESSAGE_USAGE)); + assertParseFailure(parser, "1 32/01/2018 11:11 1haam tuition homework", + String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddTuitionTaskCommand.MESSAGE_USAGE)); + + // Invalid date + assertParseFailure(parser, "1 29/02/2018 11:11 1h30m tuition homework", MESSAGE_INVALID_DATE_TIME); + assertParseFailure(parser, "1 31/04/2018 11:11 1h30m tuition homework", MESSAGE_INVALID_DATE_TIME); + assertParseFailure(parser, "1 32/01/2018 11:11 1h30m tuition homework", MESSAGE_INVALID_DATE_TIME); + + // Invalid time + assertParseFailure(parser, "1 11/01/2018 24:00 1h30m tuition homework", MESSAGE_INVALID_DATE_TIME); + assertParseFailure(parser, "1 11/01/2018 11:60 1h30m tuition homework", MESSAGE_INVALID_DATE_TIME); + + // Invalid duration + assertParseFailure(parser, "1 11/01/2018 11:11 1h60m tuition homework", MESSAGE_INVALID_DURATION); + assertParseFailure(parser, "1 11/01/2018 11:11 24h0m tuition homework", MESSAGE_INVALID_DURATION); + } + + @Test + public void parse_validArgs_success() { + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd/MM/uuuu HH:mm") + .withResolverStyle(ResolverStyle.STRICT); + + LocalDateTime taskDateTime = LocalDateTime.parse(VALID_DATE_TIME_AMY, formatter); + + // With description + assertParseSuccess(parser, "1 " + VALID_TASK_WITH_DESC_AMY, + new AddTuitionTaskCommand(INDEX_FIRST_PERSON, taskDateTime, VALID_DURATION_AMY, VALID_TASK_DESC_AMY)); + + // Without description + assertParseSuccess(parser, "1 " + VALID_TASK_WITHOUT_DESC_AMY, + new AddTuitionTaskCommand(INDEX_FIRST_PERSON, taskDateTime, VALID_DURATION_AMY, VALID_EMPTY_TASK_DESC)); + + // Valid date + taskDateTime = LocalDateTime.parse("28/02/2018 11:20", formatter); + assertParseSuccess(parser, "1 28/02/2018 11:20 " + VALID_DURATION_AMY, + new AddTuitionTaskCommand(INDEX_FIRST_PERSON, taskDateTime, VALID_DURATION_AMY, VALID_EMPTY_TASK_DESC)); + + taskDateTime = LocalDateTime.parse("29/02/2016 11:20", formatter); + assertParseSuccess(parser, "1 29/02/2016 11:20 " + VALID_DURATION_AMY, + new AddTuitionTaskCommand(INDEX_FIRST_PERSON, taskDateTime, VALID_DURATION_AMY, VALID_EMPTY_TASK_DESC)); + + taskDateTime = LocalDateTime.parse("30/04/2016 11:20", formatter); + assertParseSuccess(parser, "1 30/04/2016 11:20 " + VALID_DURATION_AMY, + new AddTuitionTaskCommand(INDEX_FIRST_PERSON, taskDateTime, VALID_DURATION_AMY, VALID_EMPTY_TASK_DESC)); + + taskDateTime = LocalDateTime.parse("31/01/2016 11:20", formatter); + assertParseSuccess(parser, "1 31/01/2016 11:20 " + VALID_DURATION_AMY, + new AddTuitionTaskCommand(INDEX_FIRST_PERSON, taskDateTime, VALID_DURATION_AMY, VALID_EMPTY_TASK_DESC)); + + // Valid Time + taskDateTime = LocalDateTime.parse("11/01/2018 00:00", formatter); + assertParseSuccess(parser, "1 11/01/2018 00:00 " + VALID_DURATION_AMY, + new AddTuitionTaskCommand(INDEX_FIRST_PERSON, taskDateTime, VALID_DURATION_AMY, VALID_EMPTY_TASK_DESC)); + } + +} diff --git a/src/test/java/seedu/address/logic/parser/AddTuteeCommandParserTest.java b/src/test/java/seedu/address/logic/parser/AddTuteeCommandParserTest.java new file mode 100644 index 000000000000..515da0ae593a --- /dev/null +++ b/src/test/java/seedu/address/logic/parser/AddTuteeCommandParserTest.java @@ -0,0 +1,258 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.commands.CommandTestUtil.ADDRESS_DESC_AMY; +import static seedu.address.logic.commands.CommandTestUtil.ADDRESS_DESC_BOB; +import static seedu.address.logic.commands.CommandTestUtil.EDUCATION_LEVEL_DESC_AMY; +import static seedu.address.logic.commands.CommandTestUtil.EDUCATION_LEVEL_DESC_BOB; +import static seedu.address.logic.commands.CommandTestUtil.EMAIL_DESC_AMY; +import static seedu.address.logic.commands.CommandTestUtil.EMAIL_DESC_BOB; +import static seedu.address.logic.commands.CommandTestUtil.GRADE_DESC_AMY; +import static seedu.address.logic.commands.CommandTestUtil.GRADE_DESC_BOB; +import static seedu.address.logic.commands.CommandTestUtil.INVALID_ADDRESS_DESC; +import static seedu.address.logic.commands.CommandTestUtil.INVALID_EDUCATION_LEVEL; +import static seedu.address.logic.commands.CommandTestUtil.INVALID_EMAIL_DESC; +import static seedu.address.logic.commands.CommandTestUtil.INVALID_GRADE_DESC; +import static seedu.address.logic.commands.CommandTestUtil.INVALID_NAME_DESC; +import static seedu.address.logic.commands.CommandTestUtil.INVALID_PHONE_DESC; +import static seedu.address.logic.commands.CommandTestUtil.INVALID_SCHOOL; +import static seedu.address.logic.commands.CommandTestUtil.INVALID_SUBJECT_DESC; +import static seedu.address.logic.commands.CommandTestUtil.INVALID_TAG_DESC; +import static seedu.address.logic.commands.CommandTestUtil.NAME_DESC_AMY; +import static seedu.address.logic.commands.CommandTestUtil.NAME_DESC_BOB; +import static seedu.address.logic.commands.CommandTestUtil.PHONE_DESC_AMY; +import static seedu.address.logic.commands.CommandTestUtil.PHONE_DESC_BOB; +import static seedu.address.logic.commands.CommandTestUtil.PREAMBLE_NON_EMPTY; +import static seedu.address.logic.commands.CommandTestUtil.PREAMBLE_WHITESPACE; +import static seedu.address.logic.commands.CommandTestUtil.SCHOOL_DESC_AMY; +import static seedu.address.logic.commands.CommandTestUtil.SCHOOL_DESC_BOB; +import static seedu.address.logic.commands.CommandTestUtil.SUBJECT_DESC_AMY; +import static seedu.address.logic.commands.CommandTestUtil.SUBJECT_DESC_BOB; +import static seedu.address.logic.commands.CommandTestUtil.TAG_DESC_FRIEND; +import static seedu.address.logic.commands.CommandTestUtil.TAG_DESC_HUSBAND; +import static seedu.address.logic.commands.CommandTestUtil.VALID_ADDRESS_AMY; +import static seedu.address.logic.commands.CommandTestUtil.VALID_ADDRESS_BOB; +import static seedu.address.logic.commands.CommandTestUtil.VALID_EDUCATION_LEVEL_AMY; +import static seedu.address.logic.commands.CommandTestUtil.VALID_EDUCATION_LEVEL_BOB; +import static seedu.address.logic.commands.CommandTestUtil.VALID_EMAIL_AMY; +import static seedu.address.logic.commands.CommandTestUtil.VALID_EMAIL_BOB; +import static seedu.address.logic.commands.CommandTestUtil.VALID_GRADE_AMY; +import static seedu.address.logic.commands.CommandTestUtil.VALID_GRADE_BOB; +import static seedu.address.logic.commands.CommandTestUtil.VALID_NAME_AMY; +import static seedu.address.logic.commands.CommandTestUtil.VALID_NAME_BOB; +import static seedu.address.logic.commands.CommandTestUtil.VALID_PHONE_AMY; +import static seedu.address.logic.commands.CommandTestUtil.VALID_PHONE_BOB; +import static seedu.address.logic.commands.CommandTestUtil.VALID_SCHOOL_AMY; +import static seedu.address.logic.commands.CommandTestUtil.VALID_SCHOOL_BOB; +import static seedu.address.logic.commands.CommandTestUtil.VALID_SUBJECT_AMY; +import static seedu.address.logic.commands.CommandTestUtil.VALID_SUBJECT_BOB; +import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_FRIEND; +import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_HUSBAND; +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure; +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess; + +import org.junit.Test; + +import seedu.address.logic.commands.AddTuteeCommand; +import seedu.address.model.person.Address; +import seedu.address.model.person.Email; +import seedu.address.model.person.Name; +import seedu.address.model.person.Phone; +import seedu.address.model.tag.Tag; +import seedu.address.model.tutee.EducationLevel; +import seedu.address.model.tutee.Grade; +import seedu.address.model.tutee.School; +import seedu.address.model.tutee.Subject; +import seedu.address.model.tutee.Tutee; +import seedu.address.testutil.TuteeBuilder; + +//@@author ChoChihTun +/** + * Contains tests for {@code AddTuteeCommandParser}. + */ +public class AddTuteeCommandParserTest { + private AddTuteeCommandParser parser = new AddTuteeCommandParser(); + + @Test + public void parse_allFieldsPresent_success() { + Tutee expectedTutee = new TuteeBuilder().withName(VALID_NAME_BOB).withPhone(VALID_PHONE_BOB) + .withEmail(VALID_EMAIL_BOB).withAddress(VALID_ADDRESS_BOB).withSubject(VALID_SUBJECT_BOB) + .withGrade(VALID_GRADE_BOB).withEducationLevel(VALID_EDUCATION_LEVEL_BOB).withSchool(VALID_SCHOOL_BOB) + .withTags(VALID_TAG_FRIEND).build(); + + // whitespace only preamble + assertParseSuccess(parser, PREAMBLE_WHITESPACE + NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + + ADDRESS_DESC_BOB + SUBJECT_DESC_BOB + GRADE_DESC_BOB + EDUCATION_LEVEL_DESC_BOB + + SCHOOL_DESC_BOB + TAG_DESC_FRIEND, new AddTuteeCommand(expectedTutee)); + + // multiple names - last name accepted + assertParseSuccess(parser, NAME_DESC_AMY + NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + + ADDRESS_DESC_BOB + SUBJECT_DESC_BOB + GRADE_DESC_BOB + EDUCATION_LEVEL_DESC_BOB + + SCHOOL_DESC_BOB + TAG_DESC_FRIEND + TAG_DESC_FRIEND, new AddTuteeCommand(expectedTutee)); + + // multiple phones - last phone accepted + assertParseSuccess(parser, NAME_DESC_BOB + PHONE_DESC_AMY + PHONE_DESC_BOB + EMAIL_DESC_BOB + + ADDRESS_DESC_BOB + SUBJECT_DESC_BOB + GRADE_DESC_BOB + EDUCATION_LEVEL_DESC_BOB + + SCHOOL_DESC_BOB + TAG_DESC_FRIEND, new AddTuteeCommand(expectedTutee)); + + // multiple emails - last email accepted + assertParseSuccess(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_AMY + EMAIL_DESC_BOB + + ADDRESS_DESC_BOB + SUBJECT_DESC_BOB + GRADE_DESC_BOB + EDUCATION_LEVEL_DESC_BOB + + SCHOOL_DESC_BOB + TAG_DESC_FRIEND, new AddTuteeCommand(expectedTutee)); + + // multiple addresses - last address accepted + assertParseSuccess(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + ADDRESS_DESC_AMY + + ADDRESS_DESC_BOB + SUBJECT_DESC_BOB + GRADE_DESC_BOB + EDUCATION_LEVEL_DESC_BOB + + SCHOOL_DESC_BOB + TAG_DESC_FRIEND, new AddTuteeCommand(expectedTutee)); + + // multiple subjects - last subject accepted + assertParseSuccess(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + ADDRESS_DESC_BOB + + SUBJECT_DESC_AMY + SUBJECT_DESC_BOB + GRADE_DESC_BOB + EDUCATION_LEVEL_DESC_BOB + + SCHOOL_DESC_BOB + TAG_DESC_FRIEND, new AddTuteeCommand(expectedTutee)); + + // multiple grades - last grade accepted + assertParseSuccess(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + ADDRESS_DESC_BOB + + SUBJECT_DESC_BOB + GRADE_DESC_AMY + GRADE_DESC_BOB + EDUCATION_LEVEL_DESC_BOB + + SCHOOL_DESC_BOB + TAG_DESC_FRIEND, new AddTuteeCommand(expectedTutee)); + + // multiple education levels - last education level accepted + assertParseSuccess(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + ADDRESS_DESC_BOB + + SUBJECT_DESC_BOB + GRADE_DESC_BOB + EDUCATION_LEVEL_DESC_AMY + EDUCATION_LEVEL_DESC_BOB + + SCHOOL_DESC_BOB + TAG_DESC_FRIEND, new AddTuteeCommand(expectedTutee)); + + // multiple schools - last school accepted + assertParseSuccess(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + ADDRESS_DESC_BOB + + SUBJECT_DESC_BOB + GRADE_DESC_BOB + EDUCATION_LEVEL_DESC_BOB + SCHOOL_DESC_AMY + + SCHOOL_DESC_BOB + TAG_DESC_FRIEND, new AddTuteeCommand(expectedTutee)); + + // multiple tags - all accepted + Tutee expectedTuteeMultipleTags = new TuteeBuilder().withName(VALID_NAME_BOB).withPhone(VALID_PHONE_BOB) + .withEmail(VALID_EMAIL_BOB).withAddress(VALID_ADDRESS_BOB).withSubject(VALID_SUBJECT_BOB) + .withGrade(VALID_GRADE_BOB).withEducationLevel(VALID_EDUCATION_LEVEL_BOB).withSchool(VALID_SCHOOL_BOB) + .withTags(VALID_TAG_FRIEND, VALID_TAG_HUSBAND).build(); + assertParseSuccess(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + ADDRESS_DESC_BOB + + SUBJECT_DESC_BOB + GRADE_DESC_BOB + EDUCATION_LEVEL_DESC_BOB + SCHOOL_DESC_BOB + TAG_DESC_FRIEND + + TAG_DESC_HUSBAND + TAG_DESC_FRIEND, new AddTuteeCommand(expectedTuteeMultipleTags)); + } + + @Test + public void parse_optionalFieldsMissing_success() { + // zero tags + Tutee expectedTutee = new TuteeBuilder().withName(VALID_NAME_AMY).withPhone(VALID_PHONE_AMY) + .withEmail(VALID_EMAIL_AMY).withAddress(VALID_ADDRESS_AMY).withSubject(VALID_SUBJECT_AMY) + .withGrade(VALID_GRADE_AMY).withEducationLevel(VALID_EDUCATION_LEVEL_AMY) + .withSchool(VALID_SCHOOL_AMY).withTags().build(); + assertParseSuccess(parser, NAME_DESC_AMY + PHONE_DESC_AMY + EMAIL_DESC_AMY + ADDRESS_DESC_AMY + + SUBJECT_DESC_AMY + GRADE_DESC_AMY + EDUCATION_LEVEL_DESC_AMY + SCHOOL_DESC_AMY, + new AddTuteeCommand(expectedTutee)); + } + + @Test + public void parse_compulsoryFieldMissing_failure() { + String expectedMessage = String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddTuteeCommand.MESSAGE_USAGE); + + // missing name prefix + assertParseFailure(parser, VALID_NAME_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + ADDRESS_DESC_BOB + + SUBJECT_DESC_BOB + GRADE_DESC_BOB + EDUCATION_LEVEL_DESC_BOB + SCHOOL_DESC_BOB, + expectedMessage); + + // missing phone prefix + assertParseFailure(parser, NAME_DESC_BOB + VALID_PHONE_BOB + EMAIL_DESC_BOB + ADDRESS_DESC_BOB + + SUBJECT_DESC_BOB + GRADE_DESC_BOB + EDUCATION_LEVEL_DESC_BOB + SCHOOL_DESC_BOB, + expectedMessage); + + // missing email prefix + assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + VALID_EMAIL_BOB + ADDRESS_DESC_BOB + + SUBJECT_DESC_BOB + GRADE_DESC_BOB + EDUCATION_LEVEL_DESC_BOB + SCHOOL_DESC_BOB, + expectedMessage); + + // missing address prefix + assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + VALID_ADDRESS_BOB + + SUBJECT_DESC_BOB + GRADE_DESC_BOB + EDUCATION_LEVEL_DESC_BOB + SCHOOL_DESC_BOB, + expectedMessage); + + // all prefixes missing + assertParseFailure(parser, VALID_NAME_BOB + VALID_PHONE_BOB + VALID_EMAIL_BOB + VALID_ADDRESS_BOB + + SUBJECT_DESC_BOB + GRADE_DESC_BOB + EDUCATION_LEVEL_DESC_BOB + SCHOOL_DESC_BOB, + expectedMessage); + + // missing subject prefix + assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + ADDRESS_DESC_BOB + + GRADE_DESC_BOB + EDUCATION_LEVEL_DESC_BOB + SCHOOL_DESC_BOB, + expectedMessage); + + // missing grade prefix + assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + ADDRESS_DESC_BOB + + SUBJECT_DESC_BOB + EDUCATION_LEVEL_DESC_BOB + SCHOOL_DESC_BOB, + expectedMessage); + + // missing education level prefix + assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + ADDRESS_DESC_BOB + + SUBJECT_DESC_BOB + GRADE_DESC_BOB + SCHOOL_DESC_BOB, + expectedMessage); + + // missing school prefix + assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + ADDRESS_DESC_BOB + + SUBJECT_DESC_BOB + GRADE_DESC_BOB + EDUCATION_LEVEL_DESC_BOB, + expectedMessage); + } + + @Test + public void parse_invalidValue_failure() { + // invalid name + assertParseFailure(parser, INVALID_NAME_DESC + PHONE_DESC_BOB + EMAIL_DESC_BOB + ADDRESS_DESC_BOB + + SUBJECT_DESC_BOB + GRADE_DESC_BOB + EDUCATION_LEVEL_DESC_BOB + SCHOOL_DESC_BOB + + TAG_DESC_HUSBAND + TAG_DESC_FRIEND, Name.MESSAGE_NAME_CONSTRAINTS); + + // invalid phone + assertParseFailure(parser, NAME_DESC_BOB + INVALID_PHONE_DESC + EMAIL_DESC_BOB + ADDRESS_DESC_BOB + + SUBJECT_DESC_BOB + GRADE_DESC_BOB + EDUCATION_LEVEL_DESC_BOB + SCHOOL_DESC_BOB + + TAG_DESC_HUSBAND + TAG_DESC_FRIEND, Phone.MESSAGE_PHONE_CONSTRAINTS); + + // invalid email + assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + INVALID_EMAIL_DESC + ADDRESS_DESC_BOB + + SUBJECT_DESC_BOB + GRADE_DESC_BOB + EDUCATION_LEVEL_DESC_BOB + SCHOOL_DESC_BOB + + TAG_DESC_HUSBAND + TAG_DESC_FRIEND, Email.MESSAGE_EMAIL_CONSTRAINTS); + + // invalid address + assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + INVALID_ADDRESS_DESC + + SUBJECT_DESC_BOB + GRADE_DESC_BOB + EDUCATION_LEVEL_DESC_BOB + SCHOOL_DESC_BOB + + TAG_DESC_HUSBAND + TAG_DESC_FRIEND, Address.MESSAGE_ADDRESS_CONSTRAINTS); + + // invalid subject + assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + ADDRESS_DESC_BOB + + INVALID_SUBJECT_DESC + GRADE_DESC_BOB + EDUCATION_LEVEL_DESC_BOB + SCHOOL_DESC_BOB + + TAG_DESC_HUSBAND + TAG_DESC_FRIEND, Subject.MESSAGE_SUBJECT_CONSTRAINTS); + + // invalid grade + assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + ADDRESS_DESC_BOB + + SUBJECT_DESC_BOB + INVALID_GRADE_DESC + EDUCATION_LEVEL_DESC_BOB + SCHOOL_DESC_BOB + + TAG_DESC_HUSBAND + TAG_DESC_FRIEND, Grade.MESSAGE_GRADE_CONSTRAINTS); + + // invalid education level + assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + ADDRESS_DESC_BOB + + SUBJECT_DESC_BOB + GRADE_DESC_BOB + INVALID_EDUCATION_LEVEL + SCHOOL_DESC_BOB + + TAG_DESC_HUSBAND + TAG_DESC_FRIEND, EducationLevel.MESSAGE_EDUCATION_LEVEL_CONSTRAINTS); + + // invalid school + assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + ADDRESS_DESC_BOB + + SUBJECT_DESC_BOB + GRADE_DESC_BOB + EDUCATION_LEVEL_DESC_BOB + INVALID_SCHOOL + + TAG_DESC_HUSBAND + TAG_DESC_FRIEND, School.MESSAGE_SCHOOL_CONSTRAINTS); + + // invalid tag + assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + ADDRESS_DESC_BOB + + SUBJECT_DESC_BOB + GRADE_DESC_BOB + EDUCATION_LEVEL_DESC_BOB + SCHOOL_DESC_BOB + + INVALID_TAG_DESC + VALID_TAG_FRIEND, Tag.MESSAGE_TAG_CONSTRAINTS); + + // three invalid values, only first invalid value reported + assertParseFailure(parser, INVALID_NAME_DESC + PHONE_DESC_BOB + EMAIL_DESC_BOB + INVALID_ADDRESS_DESC + + SUBJECT_DESC_BOB + GRADE_DESC_BOB + INVALID_EDUCATION_LEVEL + SCHOOL_DESC_BOB, + Name.MESSAGE_NAME_CONSTRAINTS); + + // non-empty preamble + assertParseFailure(parser, PREAMBLE_NON_EMPTY + NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + + ADDRESS_DESC_BOB + SUBJECT_DESC_BOB + GRADE_DESC_BOB + INVALID_EDUCATION_LEVEL + + SCHOOL_DESC_BOB + TAG_DESC_HUSBAND + TAG_DESC_FRIEND, + String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddTuteeCommand.MESSAGE_USAGE)); + } +} diff --git a/src/test/java/seedu/address/logic/parser/AddressBookParserTest.java b/src/test/java/seedu/address/logic/parser/AddressBookParserTest.java index 7466da232666..bcd9bc4b438e 100644 --- a/src/test/java/seedu/address/logic/parser/AddressBookParserTest.java +++ b/src/test/java/seedu/address/logic/parser/AddressBookParserTest.java @@ -5,35 +5,35 @@ import static org.junit.Assert.fail; import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; import static seedu.address.commons.core.Messages.MESSAGE_UNKNOWN_COMMAND; +import static seedu.address.model.person.PersonSortUtil.CATEGORY_NAME; import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON; -import java.util.Arrays; -import java.util.List; -import java.util.stream.Collectors; - import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; import seedu.address.logic.commands.AddCommand; +import seedu.address.logic.commands.AddPersonalTaskCommand; import seedu.address.logic.commands.ClearCommand; import seedu.address.logic.commands.DeleteCommand; import seedu.address.logic.commands.EditCommand; import seedu.address.logic.commands.EditCommand.EditPersonDescriptor; import seedu.address.logic.commands.ExitCommand; -import seedu.address.logic.commands.FindCommand; +import seedu.address.logic.commands.FindPersonCommand; import seedu.address.logic.commands.HelpCommand; import seedu.address.logic.commands.HistoryCommand; import seedu.address.logic.commands.ListCommand; import seedu.address.logic.commands.RedoCommand; -import seedu.address.logic.commands.SelectCommand; +import seedu.address.logic.commands.SortPersonCommand; import seedu.address.logic.commands.UndoCommand; import seedu.address.logic.parser.exceptions.ParseException; -import seedu.address.model.person.NameContainsKeywordsPredicate; import seedu.address.model.person.Person; +import seedu.address.model.personal.PersonalTask; import seedu.address.testutil.EditPersonDescriptorBuilder; import seedu.address.testutil.PersonBuilder; import seedu.address.testutil.PersonUtil; +import seedu.address.testutil.TaskBuilder; +import seedu.address.testutil.TaskUtil; public class AddressBookParserTest { @Rule @@ -48,26 +48,44 @@ public void parseCommand_add() throws Exception { assertEquals(new AddCommand(person), command); } + @Test + public void parseCommand_addPersonalTask() throws Exception { + PersonalTask task = new TaskBuilder().buildPersonalTask(); + AddPersonalTaskCommand command = (AddPersonalTaskCommand) parser + .parseCommand(TaskUtil.getAddPersonalTaskCommand(task)); + assertEquals(new AddPersonalTaskCommand(task), command); + } + @Test public void parseCommand_clear() throws Exception { assertTrue(parser.parseCommand(ClearCommand.COMMAND_WORD) instanceof ClearCommand); assertTrue(parser.parseCommand(ClearCommand.COMMAND_WORD + " 3") instanceof ClearCommand); + assertTrue(parser.parseCommand(ClearCommand.COMMAND_ALIAS) instanceof ClearCommand); + assertTrue(parser.parseCommand(ClearCommand.COMMAND_ALIAS + " 3") instanceof ClearCommand); } @Test public void parseCommand_delete() throws Exception { - DeleteCommand command = (DeleteCommand) parser.parseCommand( + DeleteCommand commandWord = (DeleteCommand) parser.parseCommand( DeleteCommand.COMMAND_WORD + " " + INDEX_FIRST_PERSON.getOneBased()); - assertEquals(new DeleteCommand(INDEX_FIRST_PERSON), command); + assertEquals(new DeleteCommand(INDEX_FIRST_PERSON), commandWord); + + DeleteCommand commandAlias = (DeleteCommand) parser.parseCommand( + DeleteCommand.COMMAND_ALIAS + " " + INDEX_FIRST_PERSON.getOneBased()); + assertEquals(new DeleteCommand(INDEX_FIRST_PERSON), commandAlias); } @Test public void parseCommand_edit() throws Exception { Person person = new PersonBuilder().build(); EditPersonDescriptor descriptor = new EditPersonDescriptorBuilder(person).build(); - EditCommand command = (EditCommand) parser.parseCommand(EditCommand.COMMAND_WORD + " " + EditCommand commandWord = (EditCommand) parser.parseCommand(EditCommand.COMMAND_WORD + " " + + INDEX_FIRST_PERSON.getOneBased() + " " + PersonUtil.getPersonDetails(person)); + assertEquals(new EditCommand(INDEX_FIRST_PERSON, descriptor), commandWord); + + EditCommand commandAlias = (EditCommand) parser.parseCommand(EditCommand.COMMAND_ALIAS + " " + INDEX_FIRST_PERSON.getOneBased() + " " + PersonUtil.getPersonDetails(person)); - assertEquals(new EditCommand(INDEX_FIRST_PERSON, descriptor), command); + assertEquals(new EditCommand(INDEX_FIRST_PERSON, descriptor), commandAlias); } @Test @@ -78,10 +96,21 @@ public void parseCommand_exit() throws Exception { @Test public void parseCommand_find() throws Exception { - List keywords = Arrays.asList("foo", "bar", "baz"); - FindCommand command = (FindCommand) parser.parseCommand( - FindCommand.COMMAND_WORD + " " + keywords.stream().collect(Collectors.joining(" "))); - assertEquals(new FindCommand(new NameContainsKeywordsPredicate(keywords)), command); + String[] keywords = {"foo", "bar", "baz"}; + FindPersonCommand commandWord = (FindPersonCommand) parser.parseCommand( + FindPersonCommand.COMMAND_WORD + " " + CATEGORY_NAME + " foo bar baz"); + assertEquals(new FindPersonCommand(CATEGORY_NAME, keywords), commandWord); + + FindPersonCommand commandAlias = (FindPersonCommand) parser.parseCommand( + FindPersonCommand.COMMAND_ALIAS + " " + CATEGORY_NAME + " foo bar baz"); + assertEquals(new FindPersonCommand(CATEGORY_NAME, keywords), commandAlias); + } + + @Test + public void parseCommand_sort() throws Exception { + SortPersonCommand commandWord = (SortPersonCommand) parser.parseCommand( + SortPersonCommand.COMMAND_WORD + " " + CATEGORY_NAME); + assertEquals(new SortPersonCommand(CATEGORY_NAME), commandWord); } @Test @@ -94,6 +123,8 @@ public void parseCommand_help() throws Exception { public void parseCommand_history() throws Exception { assertTrue(parser.parseCommand(HistoryCommand.COMMAND_WORD) instanceof HistoryCommand); assertTrue(parser.parseCommand(HistoryCommand.COMMAND_WORD + " 3") instanceof HistoryCommand); + assertTrue(parser.parseCommand(HistoryCommand.COMMAND_ALIAS) instanceof HistoryCommand); + assertTrue(parser.parseCommand(HistoryCommand.COMMAND_ALIAS + " 3") instanceof HistoryCommand); try { parser.parseCommand("histories"); @@ -107,25 +138,24 @@ public void parseCommand_history() throws Exception { public void parseCommand_list() throws Exception { assertTrue(parser.parseCommand(ListCommand.COMMAND_WORD) instanceof ListCommand); assertTrue(parser.parseCommand(ListCommand.COMMAND_WORD + " 3") instanceof ListCommand); - } - - @Test - public void parseCommand_select() throws Exception { - SelectCommand command = (SelectCommand) parser.parseCommand( - SelectCommand.COMMAND_WORD + " " + INDEX_FIRST_PERSON.getOneBased()); - assertEquals(new SelectCommand(INDEX_FIRST_PERSON), command); + assertTrue(parser.parseCommand(ListCommand.COMMAND_ALIAS) instanceof ListCommand); + assertTrue(parser.parseCommand(ListCommand.COMMAND_ALIAS + " 3") instanceof ListCommand); } @Test public void parseCommand_redoCommandWord_returnsRedoCommand() throws Exception { assertTrue(parser.parseCommand(RedoCommand.COMMAND_WORD) instanceof RedoCommand); assertTrue(parser.parseCommand("redo 1") instanceof RedoCommand); + assertTrue(parser.parseCommand(RedoCommand.COMMAND_ALIAS) instanceof RedoCommand); + assertTrue(parser.parseCommand("r 1") instanceof RedoCommand); } @Test public void parseCommand_undoCommandWord_returnsUndoCommand() throws Exception { assertTrue(parser.parseCommand(UndoCommand.COMMAND_WORD) instanceof UndoCommand); assertTrue(parser.parseCommand("undo 3") instanceof UndoCommand); + assertTrue(parser.parseCommand(UndoCommand.COMMAND_ALIAS) instanceof UndoCommand); + assertTrue(parser.parseCommand("u 3") instanceof UndoCommand); } @Test diff --git a/src/test/java/seedu/address/logic/parser/ChangeCommandParserTest.java b/src/test/java/seedu/address/logic/parser/ChangeCommandParserTest.java new file mode 100644 index 000000000000..1ee09508c679 --- /dev/null +++ b/src/test/java/seedu/address/logic/parser/ChangeCommandParserTest.java @@ -0,0 +1,103 @@ +package seedu.address.logic.parser; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure; + +import org.junit.Before; +import org.junit.Test; + +import seedu.address.logic.commands.ChangeCommand; +import seedu.address.testutil.Assert; + +//@@author ChoChihTun +public class ChangeCommandParserTest { + private static final String DAY = "d"; + private static final String WEEK = "w"; + private static final String MONTH = "m"; + private static final String YEAR = "y"; + private ChangeCommandParser parser = new ChangeCommandParser(); + private ChangeCommand changeCommand = new ChangeCommand(DAY); // Set an initial time unit to check against + + + @Test + public void parse_validArgs_returnsChangeCommand() throws Exception { + // get the initial time unit, d + String initialTimeUnit = ChangeCommand.getTimeUnit(); + + // Change time unit to w + ChangeCommand expectedTimeUnit = new ChangeCommand(WEEK); + ChangeCommand changeToInitialTimeUnit = new ChangeCommand(initialTimeUnit); // Change to initial time unit + assertEquals(expectedTimeUnit, parser.parse(WEEK)); + + // Change time unit to m + expectedTimeUnit = new ChangeCommand(MONTH); + changeToInitialTimeUnit = new ChangeCommand(initialTimeUnit); // Change to initial time unit + assertEquals(expectedTimeUnit, parser.parse(MONTH)); + + // Change time unit to y + expectedTimeUnit = new ChangeCommand(YEAR); + changeToInitialTimeUnit = new ChangeCommand(initialTimeUnit); // Change to initial time unit + assertEquals(expectedTimeUnit, parser.parse(YEAR)); + } + + @Test + public void parse_invalidArgs_throwsParseException() { + assertParseFailure(parser, "a", String.format(MESSAGE_INVALID_COMMAND_FORMAT, ChangeCommand.MESSAGE_USAGE)); + assertParseFailure(parser, "D", String.format(MESSAGE_INVALID_COMMAND_FORMAT, ChangeCommand.MESSAGE_USAGE)); + assertParseFailure(parser, "@", String.format(MESSAGE_INVALID_COMMAND_FORMAT, ChangeCommand.MESSAGE_USAGE)); + assertParseFailure(parser, "1", String.format(MESSAGE_INVALID_COMMAND_FORMAT, ChangeCommand.MESSAGE_USAGE)); + assertParseFailure(parser, "", String.format(MESSAGE_INVALID_COMMAND_FORMAT, ChangeCommand.MESSAGE_USAGE)); + } + + @Test + public void parse_sameViewPageTimeUnit_throwsSameTimeUnitException() { + assertParseFailure(parser, "d", String.format(ChangeCommand.MESSAGE_SAME_VIEW)); + } + + @Before + public void isValidTimeUnit() { + // null time unit + Assert.assertThrows(NullPointerException.class, () -> ChangeCommandParser.isValidTimeUnit(null)); + + // invalid time unit + assertFalse(ChangeCommandParser.isValidTimeUnit("")); // empty string + assertFalse(ChangeCommandParser.isValidTimeUnit(" ")); // space only + assertFalse(ChangeCommandParser.isValidTimeUnit("#")); // special characters + assertFalse(ChangeCommandParser.isValidTimeUnit("day")); // full time unit name + assertFalse(ChangeCommandParser.isValidTimeUnit("1")); // numbers + assertFalse(ChangeCommandParser.isValidTimeUnit("a")); // contains invalid alphabets + assertFalse(ChangeCommandParser.isValidTimeUnit("D")); // Capital + assertFalse(ChangeCommandParser.isValidTimeUnit(" d ")); // contains space + + // valid time unit + assertTrue(ChangeCommandParser.isValidTimeUnit(DAY)); // day + assertTrue(ChangeCommandParser.isValidTimeUnit(WEEK)); // week + assertTrue(ChangeCommandParser.isValidTimeUnit(MONTH)); // month + assertTrue(ChangeCommandParser.isValidTimeUnit(YEAR)); // year + } + + @Test + public void isTimeUnitClash() { + // All time units' validity are checked in isValidTimeUnit() + + // There is a clash of time unit + assertTrue(ChangeCommandParser.isTimeUnitClash("d")); + + // No clash in time unit + assertFalse(ChangeCommandParser.isTimeUnitClash("w")); + assertFalse(ChangeCommandParser.isTimeUnitClash("m")); + assertFalse(ChangeCommandParser.isTimeUnitClash("y")); + + // change current time unit to w + changeCommand = new ChangeCommand("w"); + + // There is a clash of time unit for w now + assertTrue(ChangeCommandParser.isTimeUnitClash("w")); + + // d is no longer clash + assertFalse(ChangeCommandParser.isTimeUnitClash("d")); + } +} diff --git a/src/test/java/seedu/address/logic/parser/EditCommandParserTest.java b/src/test/java/seedu/address/logic/parser/EditCommandParserTest.java index 24c138b41a7f..3631f9e75d44 100644 --- a/src/test/java/seedu/address/logic/parser/EditCommandParserTest.java +++ b/src/test/java/seedu/address/logic/parser/EditCommandParserTest.java @@ -3,8 +3,12 @@ import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; import static seedu.address.logic.commands.CommandTestUtil.ADDRESS_DESC_AMY; import static seedu.address.logic.commands.CommandTestUtil.ADDRESS_DESC_BOB; +import static seedu.address.logic.commands.CommandTestUtil.EDUCATION_LEVEL_DESC_AMY; +import static seedu.address.logic.commands.CommandTestUtil.EDUCATION_LEVEL_DESC_BOB; import static seedu.address.logic.commands.CommandTestUtil.EMAIL_DESC_AMY; import static seedu.address.logic.commands.CommandTestUtil.EMAIL_DESC_BOB; +import static seedu.address.logic.commands.CommandTestUtil.GRADE_DESC_AMY; +import static seedu.address.logic.commands.CommandTestUtil.GRADE_DESC_BOB; import static seedu.address.logic.commands.CommandTestUtil.INVALID_ADDRESS_DESC; import static seedu.address.logic.commands.CommandTestUtil.INVALID_EMAIL_DESC; import static seedu.address.logic.commands.CommandTestUtil.INVALID_NAME_DESC; @@ -13,15 +17,27 @@ import static seedu.address.logic.commands.CommandTestUtil.NAME_DESC_AMY; import static seedu.address.logic.commands.CommandTestUtil.PHONE_DESC_AMY; import static seedu.address.logic.commands.CommandTestUtil.PHONE_DESC_BOB; +import static seedu.address.logic.commands.CommandTestUtil.SCHOOL_DESC_AMY; +import static seedu.address.logic.commands.CommandTestUtil.SCHOOL_DESC_BOB; +import static seedu.address.logic.commands.CommandTestUtil.SUBJECT_DESC_AMY; +import static seedu.address.logic.commands.CommandTestUtil.SUBJECT_DESC_BOB; import static seedu.address.logic.commands.CommandTestUtil.TAG_DESC_FRIEND; import static seedu.address.logic.commands.CommandTestUtil.TAG_DESC_HUSBAND; import static seedu.address.logic.commands.CommandTestUtil.VALID_ADDRESS_AMY; import static seedu.address.logic.commands.CommandTestUtil.VALID_ADDRESS_BOB; +import static seedu.address.logic.commands.CommandTestUtil.VALID_EDUCATION_LEVEL_AMY; +import static seedu.address.logic.commands.CommandTestUtil.VALID_EDUCATION_LEVEL_BOB; import static seedu.address.logic.commands.CommandTestUtil.VALID_EMAIL_AMY; import static seedu.address.logic.commands.CommandTestUtil.VALID_EMAIL_BOB; +import static seedu.address.logic.commands.CommandTestUtil.VALID_GRADE_AMY; +import static seedu.address.logic.commands.CommandTestUtil.VALID_GRADE_BOB; import static seedu.address.logic.commands.CommandTestUtil.VALID_NAME_AMY; import static seedu.address.logic.commands.CommandTestUtil.VALID_PHONE_AMY; import static seedu.address.logic.commands.CommandTestUtil.VALID_PHONE_BOB; +import static seedu.address.logic.commands.CommandTestUtil.VALID_SCHOOL_AMY; +import static seedu.address.logic.commands.CommandTestUtil.VALID_SCHOOL_BOB; +import static seedu.address.logic.commands.CommandTestUtil.VALID_SUBJECT_AMY; +import static seedu.address.logic.commands.CommandTestUtil.VALID_SUBJECT_BOB; import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_FRIEND; import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_HUSBAND; import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; @@ -163,18 +179,47 @@ public void parse_oneFieldSpecified_success() { descriptor = new EditPersonDescriptorBuilder().withTags(VALID_TAG_FRIEND).build(); expectedCommand = new EditCommand(targetIndex, descriptor); assertParseSuccess(parser, userInput, expectedCommand); + + //@@author ChoChihTun + // subject + userInput = targetIndex.getOneBased() + SUBJECT_DESC_AMY; + descriptor = new EditPersonDescriptorBuilder().withSubject(VALID_SUBJECT_AMY).build(); + expectedCommand = new EditCommand(targetIndex, descriptor); + assertParseSuccess(parser, userInput, expectedCommand); + + // grade + userInput = targetIndex.getOneBased() + GRADE_DESC_AMY; + descriptor = new EditPersonDescriptorBuilder().withGrade(VALID_GRADE_AMY).build(); + expectedCommand = new EditCommand(targetIndex, descriptor); + assertParseSuccess(parser, userInput, expectedCommand); + + // education level + userInput = targetIndex.getOneBased() + EDUCATION_LEVEL_DESC_AMY; + descriptor = new EditPersonDescriptorBuilder().withEducationLevel(VALID_EDUCATION_LEVEL_AMY).build(); + expectedCommand = new EditCommand(targetIndex, descriptor); + assertParseSuccess(parser, userInput, expectedCommand); + + // school + userInput = targetIndex.getOneBased() + SCHOOL_DESC_AMY; + descriptor = new EditPersonDescriptorBuilder().withSchool(VALID_SCHOOL_AMY).build(); + expectedCommand = new EditCommand(targetIndex, descriptor); + assertParseSuccess(parser, userInput, expectedCommand); + //@@author } @Test public void parse_multipleRepeatedFields_acceptsLast() { Index targetIndex = INDEX_FIRST_PERSON; - String userInput = targetIndex.getOneBased() + PHONE_DESC_AMY + ADDRESS_DESC_AMY + EMAIL_DESC_AMY - + TAG_DESC_FRIEND + PHONE_DESC_AMY + ADDRESS_DESC_AMY + EMAIL_DESC_AMY + TAG_DESC_FRIEND - + PHONE_DESC_BOB + ADDRESS_DESC_BOB + EMAIL_DESC_BOB + TAG_DESC_HUSBAND; + String userInput = targetIndex.getOneBased() + PHONE_DESC_AMY + ADDRESS_DESC_AMY + EMAIL_DESC_AMY + + TAG_DESC_FRIEND + PHONE_DESC_AMY + ADDRESS_DESC_AMY + EMAIL_DESC_AMY + SUBJECT_DESC_AMY + + GRADE_DESC_AMY + EDUCATION_LEVEL_DESC_AMY + SCHOOL_DESC_AMY + TAG_DESC_FRIEND + + PHONE_DESC_BOB + ADDRESS_DESC_BOB + EMAIL_DESC_BOB + SUBJECT_DESC_BOB + GRADE_DESC_BOB + + EDUCATION_LEVEL_DESC_BOB + SCHOOL_DESC_BOB + TAG_DESC_HUSBAND; EditPersonDescriptor descriptor = new EditPersonDescriptorBuilder().withPhone(VALID_PHONE_BOB) .withEmail(VALID_EMAIL_BOB).withAddress(VALID_ADDRESS_BOB).withTags(VALID_TAG_FRIEND, VALID_TAG_HUSBAND) - .build(); + .withSubject(VALID_SUBJECT_BOB).withGrade(VALID_GRADE_BOB).withEducationLevel(VALID_EDUCATION_LEVEL_BOB) + .withSchool(VALID_SCHOOL_BOB).build(); EditCommand expectedCommand = new EditCommand(targetIndex, descriptor); assertParseSuccess(parser, userInput, expectedCommand); diff --git a/src/test/java/seedu/address/logic/parser/FindCommandParserTest.java b/src/test/java/seedu/address/logic/parser/FindCommandParserTest.java deleted file mode 100644 index e65143d3b7b0..000000000000 --- a/src/test/java/seedu/address/logic/parser/FindCommandParserTest.java +++ /dev/null @@ -1,34 +0,0 @@ -package seedu.address.logic.parser; - -import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; -import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure; -import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess; - -import java.util.Arrays; - -import org.junit.Test; - -import seedu.address.logic.commands.FindCommand; -import seedu.address.model.person.NameContainsKeywordsPredicate; - -public class FindCommandParserTest { - - private FindCommandParser parser = new FindCommandParser(); - - @Test - public void parse_emptyArg_throwsParseException() { - assertParseFailure(parser, " ", String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindCommand.MESSAGE_USAGE)); - } - - @Test - public void parse_validArgs_returnsFindCommand() { - // no leading and trailing whitespaces - FindCommand expectedFindCommand = - new FindCommand(new NameContainsKeywordsPredicate(Arrays.asList("Alice", "Bob"))); - assertParseSuccess(parser, "Alice Bob", expectedFindCommand); - - // multiple whitespaces between keywords - assertParseSuccess(parser, " \n Alice \n \t Bob \t", expectedFindCommand); - } - -} diff --git a/src/test/java/seedu/address/logic/parser/FindPersonCommandParserTest.java b/src/test/java/seedu/address/logic/parser/FindPersonCommandParserTest.java new file mode 100644 index 000000000000..c9f01ac57f72 --- /dev/null +++ b/src/test/java/seedu/address/logic/parser/FindPersonCommandParserTest.java @@ -0,0 +1,85 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_FILTER_CATEGORY; +import static seedu.address.logic.commands.CommandTestUtil.VALID_EDUCATION_LEVEL_AMY; +import static seedu.address.logic.commands.CommandTestUtil.VALID_GRADE_AMY; +import static seedu.address.logic.commands.CommandTestUtil.VALID_GRADE_BOB; +import static seedu.address.logic.commands.CommandTestUtil.VALID_NAME_BOB; +import static seedu.address.logic.commands.CommandTestUtil.VALID_SCHOOL_AMY; +import static seedu.address.logic.commands.CommandTestUtil.VALID_SUBJECT_AMY; +import static seedu.address.logic.commands.CommandTestUtil.VALID_SUBJECT_BOB; +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure; +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess; +import static seedu.address.model.person.PersonSortUtil.CATEGORY_EDUCATION_LEVEL; +import static seedu.address.model.person.PersonSortUtil.CATEGORY_GRADE; +import static seedu.address.model.person.PersonSortUtil.CATEGORY_NAME; +import static seedu.address.model.person.PersonSortUtil.CATEGORY_SCHOOL; +import static seedu.address.model.person.PersonSortUtil.CATEGORY_SUBJECT; + +import org.junit.Test; + +import seedu.address.logic.commands.FindPersonCommand; + +//@@author yungyung04 +/** + * Contains tests for {@code FindPersonCommandParser}. + */ +public class FindPersonCommandParserTest { + private static final int INDEX_FIRST_ELEMENT = 0; + public static final String VALID_FIRST_NAME_BOB = VALID_NAME_BOB.toLowerCase().split("\\s+")[INDEX_FIRST_ELEMENT]; + private FindPersonCommandParser parser = new FindPersonCommandParser(); + + private final String[] nameKeywords = {VALID_FIRST_NAME_BOB}; + private final String[] educationLevelKeywords = {VALID_EDUCATION_LEVEL_AMY.toLowerCase()}; + private final String[] gradeKeywords = {VALID_GRADE_AMY.toLowerCase(), VALID_GRADE_BOB.toLowerCase()}; + private final String[] schoolKeywords = VALID_SCHOOL_AMY.toLowerCase().split("\\s+"); + private final String[] subjectKeywords = {VALID_SUBJECT_AMY.toLowerCase(), VALID_SUBJECT_BOB.toLowerCase()}; + + private final String invalidCategory = "age"; + + @Test + public void parse_invalidArg_throwsParseException() { + //empty input + assertParseFailure(parser, " ", + String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindPersonCommand.MESSAGE_USAGE)); + + //not enough arguments + assertParseFailure(parser, CATEGORY_GRADE, + String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindPersonCommand.MESSAGE_USAGE)); + + //invalid category + assertParseFailure(parser, invalidCategory + " " + schoolKeywords[INDEX_FIRST_ELEMENT], + String.format(MESSAGE_INVALID_FILTER_CATEGORY, FindPersonCommand.MESSAGE_USAGE)); + } + + @Test + public void parse_validArgs_returnsFindCommand() { + // filter by name using a single keyword + FindPersonCommand expectedFindName = new FindPersonCommand(CATEGORY_NAME, nameKeywords); + assertParseSuccess(parser, CATEGORY_NAME + " Bob", expectedFindName); + + // filter by education level using a single keyword + FindPersonCommand expectedFindEducatonLevel = + new FindPersonCommand(CATEGORY_EDUCATION_LEVEL, educationLevelKeywords); + assertParseSuccess(parser, + CATEGORY_EDUCATION_LEVEL + " " + VALID_EDUCATION_LEVEL_AMY, expectedFindEducatonLevel); + + // filter by grade using 2 different keywords + FindPersonCommand expectedFindGrade = new FindPersonCommand(CATEGORY_GRADE, gradeKeywords); + assertParseSuccess(parser, CATEGORY_GRADE + " " + VALID_GRADE_AMY + + " " + VALID_GRADE_BOB, expectedFindGrade); + + // filter by school using multiple keywords from a single school + FindPersonCommand expectedFindSchool = new FindPersonCommand(CATEGORY_SCHOOL, schoolKeywords); + assertParseSuccess(parser, CATEGORY_SCHOOL + " " + VALID_SCHOOL_AMY, expectedFindSchool); + + // filter by subject using 2 different keywords + FindPersonCommand expectedFindSubject = new FindPersonCommand(CATEGORY_SUBJECT, subjectKeywords); + assertParseSuccess(parser, CATEGORY_SUBJECT + " " + VALID_SUBJECT_AMY + + " " + VALID_SUBJECT_BOB, expectedFindSubject); + + // multiple whitespaces between keywords + assertParseSuccess(parser, CATEGORY_NAME + " \n\t " + "Bob", expectedFindName); + } +} diff --git a/src/test/java/seedu/address/logic/parser/NaturalLanguageIdentifierTest.java b/src/test/java/seedu/address/logic/parser/NaturalLanguageIdentifierTest.java new file mode 100644 index 000000000000..1089b75746f9 --- /dev/null +++ b/src/test/java/seedu/address/logic/parser/NaturalLanguageIdentifierTest.java @@ -0,0 +1,98 @@ +package seedu.address.logic.parser; + +import static junit.framework.TestCase.assertEquals; +import static junit.framework.TestCase.assertTrue; +import static org.junit.Assert.assertArrayEquals; +import static seedu.address.logic.parser.NaturalLanguageIdentifier.NATURAL_CURRENT_MONTH; +import static seedu.address.logic.parser.NaturalLanguageIdentifier.NATURAL_LAST_MONTH; +import static seedu.address.logic.parser.NaturalLanguageIdentifier.NATURAL_NEXT_MONTH; +import static seedu.address.logic.parser.NaturalLanguageIdentifier.NATURAL_NOW; + +import java.time.LocalDateTime; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + + +//@@author yungyung04 +public class NaturalLanguageIdentifierTest { + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + private NaturalLanguageIdentifier identifier = NaturalLanguageIdentifier.getInstance(); + + @Test + public void getInstance_firstTimeCalled_returnInstanceOfClass() { + assertTrue(identifier instanceof NaturalLanguageIdentifier); + } + + @Test + public void getInstance_subsequentCalls_returnSameInstance() { + NaturalLanguageIdentifier identifierCopy = NaturalLanguageIdentifier.getInstance(); + assertEquals(identifier, identifierCopy); + } + + @Test + public void getMonthAsString_recognizableInput_returnMonth() { + LocalDateTime current = LocalDateTime.now(); + + //natural languages which refer to current month + assertEquals(current.getMonth().name(), identifier.getMonthAsString(NATURAL_CURRENT_MONTH)); + assertEquals(current.getMonth().name(), identifier.getMonthAsString(NATURAL_NOW)); + + //natural language which refers to last month + assertEquals(current.getMonth().minus(1).name(), identifier.getMonthAsString(NATURAL_LAST_MONTH)); + + //natural language which refers to next month + assertEquals(current.getMonth().plus(1).name(), identifier.getMonthAsString(NATURAL_NEXT_MONTH)); + } + + @Test + public void getMonthAsString_unrecognizableInput_returnInput() { + LocalDateTime current = LocalDateTime.now(); + String unrecognizable = "unrecognizable input"; + assertEquals(unrecognizable, identifier.getMonthAsString(unrecognizable)); + } + + @Test + public void getMonthAsString_nullInput_returnInput() { + LocalDateTime current = LocalDateTime.now(); + String unrecognizable = null; + thrown.expect(NullPointerException.class); + String result = identifier.getMonthAsString(unrecognizable); + } + + @Test + public void mergeTwoWordedNaturalLanguage_emptyString_returnEmptyString() { + String[] userInputs = {}; + String[] expectedResults = {}; + String[] results = identifier.mergeTwoWordedNaturalLanguage(userInputs); + assertArrayEquals(expectedResults, results); + } + + @Test + public void mergeTwoWordedNaturalLanguage_oneRecognizableElement_returnInputtedArray() { + String[] userInputs = {"this"}; + String[] expectedResults = {"this"}; + String[] results = identifier.mergeTwoWordedNaturalLanguage(userInputs); + assertArrayEquals(expectedResults, userInputs); + } + + @Test + public void mergeTwoWordedNaturalLanguage_oneUnrecognizableElement_returnInputtedArray() { + String[] userInputs = {"unrecognizable"}; + String[] expectedResults = {"unrecognizable"}; + String[] results = identifier.mergeTwoWordedNaturalLanguage(userInputs); + assertArrayEquals(expectedResults, results); + } + + @Test + public void mergeTwoWordedNaturalLanguage_multipleElements_returnMergedArray() { + String[] userInputs = {"this", "month", "today", "unrecognized", "last", "month", "unrecognized"}; + String[] expectedResults = {"this month", "today", "unrecognized", "last month", "unrecognized"}; + String[] results = identifier.mergeTwoWordedNaturalLanguage(userInputs); + assertArrayEquals(expectedResults, results); + } +} diff --git a/src/test/java/seedu/address/logic/parser/ParserUtilTest.java b/src/test/java/seedu/address/logic/parser/ParserUtilTest.java index 54516c1c5e95..ddde7841455c 100644 --- a/src/test/java/seedu/address/logic/parser/ParserUtilTest.java +++ b/src/test/java/seedu/address/logic/parser/ParserUtilTest.java @@ -5,8 +5,13 @@ import static org.junit.Assert.assertTrue; import static seedu.address.logic.parser.ParserUtil.MESSAGE_INVALID_INDEX; +import static seedu.address.logic.parser.ParserUtil.parseDateTime; +import static seedu.address.logic.parser.ParserUtil.parseDuration; +import static seedu.address.testutil.TaskUtil.FORMATTER; import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON; +import java.time.LocalDateTime; +import java.time.format.DateTimeParseException; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; @@ -18,11 +23,16 @@ import org.junit.rules.ExpectedException; import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.logic.parser.exceptions.DurationParseException; import seedu.address.model.person.Address; import seedu.address.model.person.Email; import seedu.address.model.person.Name; import seedu.address.model.person.Phone; import seedu.address.model.tag.Tag; +import seedu.address.model.tutee.EducationLevel; +import seedu.address.model.tutee.Grade; +import seedu.address.model.tutee.School; +import seedu.address.model.tutee.Subject; import seedu.address.testutil.Assert; public class ParserUtilTest { @@ -31,6 +41,12 @@ public class ParserUtilTest { private static final String INVALID_ADDRESS = " "; private static final String INVALID_EMAIL = "example.com"; private static final String INVALID_TAG = "#friend"; + private static final String INVALID_SUBJECT = "#subject"; + private static final String INVALID_GRADE = "+B"; + private static final String INVALID_EDUCATIONAL_LEVEL = "University"; + private static final String INVALID_SCHOOL = "school12"; + private static final String INVALID_TIME_UNIT = "year"; + private static final String INVALID_DURATION = "1.5h"; private static final String VALID_NAME = "Rachel Walker"; private static final String VALID_PHONE = "123456"; @@ -38,8 +54,22 @@ public class ParserUtilTest { private static final String VALID_EMAIL = "rachel@example.com"; private static final String VALID_TAG_1 = "friend"; private static final String VALID_TAG_2 = "neighbour"; - + private static final String VALID_SUBJECT = "science"; + private static final String VALID_GRADE = "B+"; + private static final String VALID_EDUCATIONAL_LEVEL = "primary"; + private static final String VALID_SCHOOL = "valid primary school"; + private static final String VALID_TIME_UNIT = "y"; + private static final String VALID_DATE = "25/04/2018"; + private static final String VALID_TIME = "08:01"; + private static final String VALID_DURATION = "1h30m"; + private static final String VALID_DESCRIPTION = "homework"; + private static final String VALID_TASK_WITHOUT_DESCRIPTION = VALID_DATE + " " + VALID_TIME + " " + VALID_DURATION; + private static final String VALID_TASK_WITH_DESCRIPTION = VALID_TASK_WITHOUT_DESCRIPTION + " " + VALID_DESCRIPTION; + + private static final String TUTEE_TAG = "Tutee"; + private static final String CAPITAL_TUTEE_TAG = "TUTEE"; private static final String WHITESPACE = " \t\r\n"; + private static final int MAXIMUM_AMOUNT_OF_PARAMETERS = 4; @Rule public final ExpectedException thrown = ExpectedException.none(); @@ -219,6 +249,7 @@ public void parseTag_validValueWithWhitespace_returnsTrimmedTag() throws Excepti assertEquals(expectedTag, ParserUtil.parseTag(tagWithWhitespace)); } + @Test public void parseTags_null_throwsNullPointerException() throws Exception { thrown.expect(NullPointerException.class); @@ -243,4 +274,314 @@ public void parseTags_collectionWithValidTags_returnsTagSet() throws Exception { assertEquals(expectedTagSet, actualTagSet); } + + //@@author ChoChihTun + @Test + public void parseTags_collectionWithValidTagsAndTuteeTag_returnsTagSet() throws Exception { + Set actualTagSet = ParserUtil.parseTags(Arrays.asList(VALID_TAG_1, CAPITAL_TUTEE_TAG)); + Set expectedTagSet = new HashSet<>(Arrays.asList(new Tag(VALID_TAG_1), new Tag(TUTEE_TAG))); + + assertEquals(expectedTagSet, actualTagSet); + } + + @Test + public void parsePersonTags_null_throwsNullPointerException() throws Exception { + thrown.expect(NullPointerException.class); + ParserUtil.parsePersonTags(null); + } + + @Test + public void parsePersonTags_collectionWithInvalidTags_throwsIllegalValueException() throws Exception { + thrown.expect(IllegalValueException.class); + ParserUtil.parsePersonTags(Arrays.asList(VALID_TAG_1, INVALID_TAG)); + } + + @Test + public void parsePersonTags_emptyCollection_returnsEmptySet() throws Exception { + assertTrue(ParserUtil.parsePersonTags(Collections.emptyList()).isEmpty()); + } + + @Test + public void parsePersonTags_collectionWithValidTags_returnsTagSet() throws Exception { + Set actualTagSet = ParserUtil.parsePersonTags(Arrays.asList(VALID_TAG_1, VALID_TAG_2)); + Set expectedTagSet = new HashSet<>(Arrays.asList(new Tag(VALID_TAG_1), new Tag(VALID_TAG_2))); + + assertEquals(expectedTagSet, actualTagSet); + } + + @Test + public void parsePersonTags_collectionWithTuteeTags_throwsIllegalValueException() throws Exception { + thrown.expect(IllegalValueException.class); + ParserUtil.parsePersonTags(Arrays.asList(VALID_TAG_1, TUTEE_TAG)); + } + + @Test + public void parseTuteeTags_null_throwsNullPointerException() throws Exception { + thrown.expect(NullPointerException.class); + ParserUtil.parseTuteeTags(null); + } + + @Test + public void parseTuteeTags_collectionWithInvalidTags_throwsIllegalValueException() throws Exception { + thrown.expect(IllegalValueException.class); + ParserUtil.parseTuteeTags(Arrays.asList(VALID_TAG_1, INVALID_TAG)); + } + + @Test + public void parseTuteeTags_emptyCollection_returnsEmptySet() throws Exception { + assertTrue(ParserUtil.parseTuteeTags(Collections.emptyList()).isEmpty()); + } + + @Test + public void parseTuteeTags_collectionWithValidTags_returnsTagSet() throws Exception { + Set actualTagSet = ParserUtil.parseTuteeTags(Arrays.asList(VALID_TAG_1, VALID_TAG_2)); + Set expectedTagSet = new HashSet<>(Arrays.asList(new Tag(VALID_TAG_1), new Tag(VALID_TAG_2))); + + assertEquals(expectedTagSet, actualTagSet); + } + + @Test + public void parseTuteeTags_collectionWithValidAndTuteeTags_returnsTagSet() throws Exception { + Set actualTagSet = ParserUtil.parseTuteeTags(Arrays.asList(VALID_TAG_1, TUTEE_TAG)); + Set expectedTagSet = new HashSet<>(Arrays.asList(new Tag(VALID_TAG_1))); + + assertEquals(expectedTagSet, actualTagSet); + } + + @Test + public void parseSubject_null_throwsNullPointerException() { + Assert.assertThrows(NullPointerException.class, () -> ParserUtil.parseSubject((String) null)); + Assert.assertThrows(NullPointerException.class, () -> ParserUtil.parseSubject((Optional) null)); + } + + @Test + public void parseSubject_invalidValue_throwsIllegalValueException() { + Assert.assertThrows(IllegalValueException.class, () -> ParserUtil.parseSubject(INVALID_SUBJECT)); + Assert.assertThrows(IllegalValueException.class, () -> ParserUtil.parseSubject(Optional.of(INVALID_SUBJECT))); + } + + @Test + public void parseSubject_optionalEmpty_returnsOptionalEmpty() throws Exception { + assertFalse(ParserUtil.parseSubject(Optional.empty()).isPresent()); + } + + @Test + public void parseSubject_validValueWithoutWhitespace_returnsSubject() throws Exception { + Subject expectedSubject = new Subject(VALID_SUBJECT); + assertEquals(expectedSubject, ParserUtil.parseSubject(VALID_SUBJECT)); + assertEquals(Optional.of(expectedSubject), ParserUtil.parseSubject(Optional.of(VALID_SUBJECT))); + } + + @Test + public void parseSubject_validValueWithWhitespace_returnsTrimmedSubject() throws Exception { + String subjectWithWhitespace = WHITESPACE + VALID_SUBJECT + WHITESPACE; + Subject expectedSubject = new Subject(VALID_SUBJECT); + assertEquals(expectedSubject, ParserUtil.parseSubject(subjectWithWhitespace)); + assertEquals(Optional.of(expectedSubject), ParserUtil.parseSubject(Optional.of(subjectWithWhitespace))); + } + + @Test + public void parseGrade_null_throwsNullPointerException() { + Assert.assertThrows(NullPointerException.class, () -> ParserUtil.parseGrade((String) null)); + Assert.assertThrows(NullPointerException.class, () -> ParserUtil.parseGrade((Optional) null)); + } + + @Test + public void parseGrade_invalidValue_throwsIllegalValueException() { + Assert.assertThrows(IllegalValueException.class, () -> ParserUtil.parseGrade(INVALID_GRADE)); + Assert.assertThrows(IllegalValueException.class, () -> ParserUtil.parseGrade(Optional.of(INVALID_GRADE))); + } + + @Test + public void parseGrade_optionalEmpty_returnsOptionalEmpty() throws Exception { + assertFalse(ParserUtil.parseGrade(Optional.empty()).isPresent()); + } + + @Test + public void parseGrade_validValueWithoutWhitespace_returnsGrade() throws Exception { + Grade expectedGrade = new Grade(VALID_GRADE); + assertEquals(expectedGrade, ParserUtil.parseGrade(VALID_GRADE)); + assertEquals(Optional.of(expectedGrade), ParserUtil.parseGrade(Optional.of(VALID_GRADE))); + } + + @Test + public void parseGrade_validValueWithWhitespace_returnsTrimmedGrade() throws Exception { + String gradeWithWhitespace = WHITESPACE + VALID_GRADE + WHITESPACE; + Grade expectedGrade = new Grade(VALID_GRADE); + assertEquals(expectedGrade, ParserUtil.parseGrade(gradeWithWhitespace)); + assertEquals(Optional.of(expectedGrade), ParserUtil.parseGrade(Optional.of(gradeWithWhitespace))); + } + + @Test + public void parseEducationLevel_null_throwsNullPointerException() { + Assert.assertThrows(NullPointerException.class, () -> ParserUtil.parseEducationLevel((String) null)); + Assert.assertThrows(NullPointerException.class, () -> ParserUtil.parseEducationLevel((Optional) null)); + } + + @Test + public void parseEducationLevel_invalidValue_throwsIllegalValueException() { + Assert.assertThrows(IllegalValueException.class, () -> ParserUtil.parseEducationLevel( + INVALID_EDUCATIONAL_LEVEL)); + Assert.assertThrows(IllegalValueException.class, () -> ParserUtil.parseEducationLevel( + Optional.of(INVALID_EDUCATIONAL_LEVEL))); + } + + @Test + public void parseEducationLevel_optionalEmpty_returnsOptionalEmpty() throws Exception { + assertFalse(ParserUtil.parseEducationLevel(Optional.empty()).isPresent()); + } + + @Test + public void parseEducationLevel_validValueWithoutWhitespace_returnsEducationLevel() throws Exception { + EducationLevel expectedEducationLevel = new EducationLevel(VALID_EDUCATIONAL_LEVEL); + assertEquals(expectedEducationLevel, ParserUtil.parseEducationLevel(VALID_EDUCATIONAL_LEVEL)); + assertEquals(Optional.of(expectedEducationLevel), ParserUtil.parseEducationLevel( + Optional.of(VALID_EDUCATIONAL_LEVEL))); + } + + @Test + public void parseEducationLevel_validValueWithWhitespace_returnsTrimmedEducationLevel() throws Exception { + String educationLevelWithWhitespace = WHITESPACE + VALID_EDUCATIONAL_LEVEL + WHITESPACE; + EducationLevel expectedEducationLevel = new EducationLevel(VALID_EDUCATIONAL_LEVEL); + assertEquals(expectedEducationLevel, ParserUtil.parseEducationLevel(educationLevelWithWhitespace)); + assertEquals(Optional.of(expectedEducationLevel), ParserUtil.parseEducationLevel( + Optional.of(educationLevelWithWhitespace))); + } + + @Test + public void parseSchool_null_throwsNullPointerException() { + Assert.assertThrows(NullPointerException.class, () -> ParserUtil.parseSchool((String) null)); + Assert.assertThrows(NullPointerException.class, () -> ParserUtil.parseSchool((Optional) null)); + } + + @Test + public void parseSchool_invalidValue_throwsIllegalValueException() { + Assert.assertThrows(IllegalValueException.class, () -> ParserUtil.parseSchool(INVALID_SCHOOL)); + Assert.assertThrows(IllegalValueException.class, () -> ParserUtil.parseSchool(Optional.of(INVALID_SCHOOL))); + } + + @Test + public void parseSchool_optionalEmpty_returnsOptionalEmpty() throws Exception { + assertFalse(ParserUtil.parseSchool(Optional.empty()).isPresent()); + } + + @Test + public void parseSchool_validValueWithoutWhitespace_returnsSchool() throws Exception { + School expectedSchool = new School(VALID_SCHOOL); + assertEquals(expectedSchool, ParserUtil.parseSchool(VALID_SCHOOL)); + assertEquals(Optional.of(expectedSchool), ParserUtil.parseSchool(Optional.of(VALID_SCHOOL))); + } + + @Test + public void parseSchool_validValueWithWhitespace_returnsTrimmedSchool() throws Exception { + String schoolWithWhitespace = WHITESPACE + VALID_SCHOOL + WHITESPACE; + School expectedSchool = new School(VALID_SCHOOL); + assertEquals(expectedSchool, ParserUtil.parseSchool(schoolWithWhitespace)); + assertEquals(Optional.of(expectedSchool), ParserUtil.parseSchool(Optional.of(schoolWithWhitespace))); + } + + @Test + public void parseTimeUnit_null_throwsNullPointerException() { + Assert.assertThrows(NullPointerException.class, () -> ParserUtil.parseTimeUnit(null)); + } + + @Test + public void parseTimeUnit_invalidValue_throwsIllegalValueException() { + Assert.assertThrows(IllegalValueException.class, () -> ParserUtil.parseTimeUnit(INVALID_TIME_UNIT)); + } + + @Test + public void parseTimeUnit_validValueWithoutWhitespace_returnsTimeUnit() throws Exception { + String expectedTimeUnit = VALID_TIME_UNIT; + assertEquals(expectedTimeUnit, ParserUtil.parseTimeUnit(VALID_TIME_UNIT)); + } + + @Test + public void parseTimeUnit_validValueWithWhitespace_returnsTrimmedTimeUnit() throws Exception { + String timeUnitWithWhitespace = WHITESPACE + VALID_TIME_UNIT + WHITESPACE; + assertEquals(VALID_TIME_UNIT, ParserUtil.parseTimeUnit(timeUnitWithWhitespace)); + } + + //@@author yungyung04 + @Test + public void parseDateTime_invalidInput_throwsDateTimeParseException() { + //null date and time + Assert.assertThrows(NullPointerException.class, () -> ParserUtil.parseDateTime(null)); + + //invalid date in non leap year + Assert.assertThrows(DateTimeParseException.class, () -> ParserUtil + .parseDateTime("29/02/2018 " + VALID_TIME)); + + //invalid date in century year + Assert.assertThrows(DateTimeParseException.class, () -> ParserUtil + .parseDateTime("29/02/1900 " + VALID_TIME)); + + //invalid date in month with 30 days + Assert.assertThrows(DateTimeParseException.class, () -> ParserUtil + .parseDateTime("31/04/2018 " + VALID_TIME)); + + //invalid date in month with 31 days + Assert.assertThrows(DateTimeParseException.class, () -> ParserUtil + .parseDateTime("32/03/2018 " + VALID_TIME)); + + //invalid hour + Assert.assertThrows(DateTimeParseException.class, () -> ParserUtil + .parseDateTime(VALID_DATE + " 25:00")); + + //invalid minute + Assert.assertThrows(DateTimeParseException.class, () -> ParserUtil + .parseDateTime(VALID_DATE + "12:60")); + } + + @Test + public void parseDateTime_validInput_parsedSuccessfully() { + //beginning of the month + LocalDateTime expectedDateTime = LocalDateTime.parse("01/10/2018 " + VALID_TIME, FORMATTER); + assertEquals(expectedDateTime, parseDateTime("01/10/2018 " + VALID_TIME)); + + //leap year + expectedDateTime = LocalDateTime.parse("29/02/2020 " + VALID_TIME, FORMATTER); + assertEquals(expectedDateTime, parseDateTime("29/02/2020 " + VALID_TIME)); + + //month with 30 days + expectedDateTime = LocalDateTime.parse("30/04/2020 " + VALID_TIME, FORMATTER); + assertEquals(expectedDateTime, parseDateTime("30/04/2020 " + VALID_TIME)); + + //month with 31 days + expectedDateTime = LocalDateTime.parse("31/03/2020 " + VALID_TIME, FORMATTER); + assertEquals(expectedDateTime, parseDateTime("31/03/2020 " + VALID_TIME)); + + //valid time at boundary value + expectedDateTime = LocalDateTime.parse(VALID_DATE + " 12:00", FORMATTER); + assertEquals(expectedDateTime, parseDateTime(VALID_DATE + " 12:00")); + } + + @Test + public void parseDuration_invalidInput_throwsDateTimeParseException() { + //null duration + Assert.assertThrows(NullPointerException.class, () -> ParserUtil.parseDuration(null)); + + //invalid duration + Assert.assertThrows(DurationParseException.class, () -> ParserUtil + .parseDuration(INVALID_DURATION)); + } + + @Test + public void parseDuration_validInput_parsedSuccessfully() throws Exception { + String expectedDuration = VALID_DURATION; + assertEquals(expectedDuration, parseDuration(VALID_DURATION)); + } + + @Test + public void parseDescription_noDescriptionWithinInput_returnsEmptyString() { + //user input without description + String[] validInputs = VALID_TASK_WITHOUT_DESCRIPTION.split("\\s+", MAXIMUM_AMOUNT_OF_PARAMETERS); + String expectedDescription = ""; + assertEquals(expectedDescription, ParserUtil.parseDescription(validInputs, MAXIMUM_AMOUNT_OF_PARAMETERS)); + + //user input with description + validInputs = VALID_TASK_WITH_DESCRIPTION.split("\\s+", MAXIMUM_AMOUNT_OF_PARAMETERS); + expectedDescription = VALID_DESCRIPTION; + assertEquals(expectedDescription, ParserUtil.parseDescription(validInputs, MAXIMUM_AMOUNT_OF_PARAMETERS)); + } } diff --git a/src/test/java/seedu/address/logic/parser/SortPersonCommandParserTest.java b/src/test/java/seedu/address/logic/parser/SortPersonCommandParserTest.java new file mode 100644 index 000000000000..cca1f6077540 --- /dev/null +++ b/src/test/java/seedu/address/logic/parser/SortPersonCommandParserTest.java @@ -0,0 +1,66 @@ +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_SORTER_CATEGORY; +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure; +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess; +import static seedu.address.model.person.PersonSortUtil.CATEGORY_EDUCATION_LEVEL; +import static seedu.address.model.person.PersonSortUtil.CATEGORY_GRADE; +import static seedu.address.model.person.PersonSortUtil.CATEGORY_NAME; +import static seedu.address.model.person.PersonSortUtil.CATEGORY_SCHOOL; +import static seedu.address.model.person.PersonSortUtil.CATEGORY_SUBJECT; + +import org.junit.Test; + +import seedu.address.logic.commands.SortPersonCommand; + +//@@author yungyung04 +/** + * Contains tests for {@code SortPersonCommandParser}. + */ +public class SortPersonCommandParserTest { + private SortPersonCommandParser parser = new SortPersonCommandParser(); + + private final String invalidCategory = "age"; + + @Test + public void parse_invalidArg_throwsParseException() { + //empty input + assertParseFailure(parser, " ", + String.format(MESSAGE_INVALID_COMMAND_FORMAT, SortPersonCommand.MESSAGE_USAGE)); + + //too many arguments + assertParseFailure(parser, CATEGORY_GRADE + " " + CATEGORY_EDUCATION_LEVEL, + String.format(MESSAGE_INVALID_COMMAND_FORMAT, SortPersonCommand.MESSAGE_USAGE)); + + //invalid category + assertParseFailure(parser, invalidCategory, + String.format(MESSAGE_INVALID_SORTER_CATEGORY, SortPersonCommand.MESSAGE_USAGE)); + } + + @Test + public void parse_validArgs_returnsFindCommand() { + // sort by name + SortPersonCommand expectedSortName = new SortPersonCommand(CATEGORY_NAME); + assertParseSuccess(parser, CATEGORY_NAME, expectedSortName); + + // sort by education level + SortPersonCommand expectedSortEducatonLevel = new SortPersonCommand(CATEGORY_EDUCATION_LEVEL); + assertParseSuccess(parser, CATEGORY_EDUCATION_LEVEL, expectedSortEducatonLevel); + + // sort by grade + SortPersonCommand expectedSortGrade = new SortPersonCommand(CATEGORY_GRADE); + assertParseSuccess(parser, CATEGORY_GRADE, expectedSortGrade); + + // sort by school + SortPersonCommand expectedSortSchool = new SortPersonCommand(CATEGORY_SCHOOL); + assertParseSuccess(parser, CATEGORY_SCHOOL, expectedSortSchool); + + // sort by subject + SortPersonCommand expectedSortSubject = new SortPersonCommand(CATEGORY_SUBJECT); + assertParseSuccess(parser, CATEGORY_SUBJECT, expectedSortSubject); + + // multiple whitespaces before and after sort category + assertParseSuccess(parser, " \n\t" + CATEGORY_NAME + "\n\t", expectedSortName); + } +} diff --git a/src/test/java/seedu/address/model/AddressBookTest.java b/src/test/java/seedu/address/model/AddressBookTest.java index bf26f68896b8..42ba0b281e71 100644 --- a/src/test/java/seedu/address/model/AddressBookTest.java +++ b/src/test/java/seedu/address/model/AddressBookTest.java @@ -1,8 +1,10 @@ package seedu.address.model; import static org.junit.Assert.assertEquals; -import static seedu.address.testutil.TypicalPersons.ALICE; -import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook; +import static seedu.address.testutil.PersonBuilder.DEFAULT_TAGS; +import static seedu.address.testutil.TypicalTasks.EXAMPLE1; +import static seedu.address.testutil.typicaladdressbook.TypicalAddressBookCompiler.getTypicalAddressBook1; +import static seedu.address.testutil.typicaladdressbook.TypicalPersons.ALICE; import java.util.ArrayList; import java.util.Arrays; @@ -19,6 +21,8 @@ import seedu.address.model.person.Person; import seedu.address.model.tag.Tag; +import seedu.address.testutil.PersonBuilder; + public class AddressBookTest { @Rule @@ -30,6 +34,7 @@ public class AddressBookTest { public void constructor() { assertEquals(Collections.emptyList(), addressBook.getPersonList()); assertEquals(Collections.emptyList(), addressBook.getTagList()); + assertEquals(Collections.emptyList(), addressBook.getTaskList()); } @Test @@ -40,7 +45,7 @@ public void resetData_null_throwsNullPointerException() { @Test public void resetData_withValidReadOnlyAddressBook_replacesData() { - AddressBook newData = getTypicalAddressBook(); + AddressBook newData = getTypicalAddressBook1(); addressBook.resetData(newData); assertEquals(newData, addressBook); } @@ -50,12 +55,25 @@ public void resetData_withDuplicatePersons_throwsAssertionError() { // Repeat ALICE twice List newPersons = Arrays.asList(ALICE, ALICE); List newTags = new ArrayList<>(ALICE.getTags()); - AddressBookStub newData = new AddressBookStub(newPersons, newTags); + List newTasks = Arrays.asList(EXAMPLE1); + AddressBookStub newData = new AddressBookStub(newPersons, newTags, newTasks); thrown.expect(AssertionError.class); addressBook.resetData(newData); } + //@@author a-shakra + @Test + public void resetData_withDuplicateTasks_throwsAssertionError() { + // Repeat EXAMPLE1 twice + List newPersons = Arrays.asList(ALICE); + List newTags = new ArrayList<>(ALICE.getTags()); + List newTasks = Arrays.asList(EXAMPLE1, EXAMPLE1); + AddressBookStub newData = new AddressBookStub(newPersons, newTags, newTasks); + thrown.expect(AssertionError.class); + addressBook.resetData(newData); + } + //@@author @Test public void getPersonList_modifyList_throwsUnsupportedOperationException() { thrown.expect(UnsupportedOperationException.class); @@ -67,6 +85,23 @@ public void getTagList_modifyList_throwsUnsupportedOperationException() { thrown.expect(UnsupportedOperationException.class); addressBook.getTagList().remove(0); } + //@@author a-shakra + @Test + public void getTaskList_modifyList_throwsUnsupportedOperationException() { + thrown.expect(UnsupportedOperationException.class); + addressBook.getTaskList().remove(0); + } + //@@author + @Test + public void removeTag_existingTag_tagRemoved() throws Exception { + Person person = new PersonBuilder().build(); + addressBook.addPerson(person); + addressBook.removeTagFromPerson(new Tag(DEFAULT_TAGS), person); + + Person expectedPerson = new PersonBuilder().withTags().build(); + + assertEquals(person, expectedPerson); + } /** * A stub ReadOnlyAddressBook whose persons and tags lists can violate interface constraints. @@ -74,10 +109,12 @@ public void getTagList_modifyList_throwsUnsupportedOperationException() { private static class AddressBookStub implements ReadOnlyAddressBook { private final ObservableList persons = FXCollections.observableArrayList(); private final ObservableList tags = FXCollections.observableArrayList(); + private final ObservableList tasks = FXCollections.observableArrayList(); - AddressBookStub(Collection persons, Collection tags) { + AddressBookStub(Collection persons, Collection tags, Collection tasks) { this.persons.setAll(persons); this.tags.setAll(tags); + this.tasks.setAll(tasks); } @Override @@ -85,6 +122,11 @@ public ObservableList getPersonList() { return persons; } + @Override + public ObservableList getTaskList() { + return tasks; + } + @Override public ObservableList getTagList() { return tags; diff --git a/src/test/java/seedu/address/model/ModelManagerTest.java b/src/test/java/seedu/address/model/ModelManagerTest.java index 59ce1b83693a..2361b1cf2375 100644 --- a/src/test/java/seedu/address/model/ModelManagerTest.java +++ b/src/test/java/seedu/address/model/ModelManagerTest.java @@ -3,8 +3,10 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS; -import static seedu.address.testutil.TypicalPersons.ALICE; -import static seedu.address.testutil.TypicalPersons.BENSON; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_TASKS; +import static seedu.address.testutil.TypicalTasks.EXAMPLE1; +import static seedu.address.testutil.typicaladdressbook.TypicalPersons.ALICE; +import static seedu.address.testutil.typicaladdressbook.TypicalPersons.BENSON; import java.util.Arrays; @@ -26,9 +28,17 @@ public void getFilteredPersonList_modifyList_throwsUnsupportedOperationException modelManager.getFilteredPersonList().remove(0); } + @Test + public void getFilteredTaskList_modifyList_throwsUnsupportedOperationException() { + ModelManager modelManager = new ModelManager(); + thrown.expect(UnsupportedOperationException.class); + modelManager.getFilteredTaskList().remove(0); + } + @Test public void equals() { - AddressBook addressBook = new AddressBookBuilder().withPerson(ALICE).withPerson(BENSON).build(); + AddressBook addressBook = new AddressBookBuilder().withPerson(ALICE).withPerson(BENSON) + .withTask(EXAMPLE1).build(); AddressBook differentAddressBook = new AddressBook(); UserPrefs userPrefs = new UserPrefs(); @@ -49,13 +59,19 @@ public void equals() { // different addressBook -> returns false assertFalse(modelManager.equals(new ModelManager(differentAddressBook, userPrefs))); - // different filteredList -> returns false + // different person filteredList -> returns false String[] keywords = ALICE.getName().fullName.split("\\s+"); modelManager.updateFilteredPersonList(new NameContainsKeywordsPredicate(Arrays.asList(keywords))); assertFalse(modelManager.equals(new ModelManager(addressBook, userPrefs))); + // different task filteredList -> returns false + String[] keywordsT = EXAMPLE1.getDescription().split("\\s+"); + modelManager.updateFilteredTaskList(new TaskContainsKeywordsPredicate(Arrays.asList(keywordsT))); + assertFalse(modelManager.equals(new ModelManager(addressBook, userPrefs))); + // resets modelManager to initial state for upcoming tests modelManager.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); + modelManager.updateFilteredTaskList(PREDICATE_SHOW_ALL_TASKS); // different userPrefs -> returns true UserPrefs differentUserPrefs = new UserPrefs(); diff --git a/src/test/java/seedu/address/model/TaskContainsKeywordsPredicateTest.java b/src/test/java/seedu/address/model/TaskContainsKeywordsPredicateTest.java new file mode 100644 index 000000000000..de27c6cd8122 --- /dev/null +++ b/src/test/java/seedu/address/model/TaskContainsKeywordsPredicateTest.java @@ -0,0 +1,63 @@ +package seedu.address.model; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import org.junit.Test; + +import seedu.address.testutil.TaskBuilder; +//@@author a-shakra +public class TaskContainsKeywordsPredicateTest { + + @Test + public void equals() { + List firstPredicateKeywordList = Collections.singletonList("first"); + List secondPredicateKeywordList = Arrays.asList("first", "second"); + + TaskContainsKeywordsPredicate firstPredicate = new TaskContainsKeywordsPredicate(firstPredicateKeywordList); + TaskContainsKeywordsPredicate secondPredicate = new TaskContainsKeywordsPredicate(secondPredicateKeywordList); + + // same object -> returns true + assertTrue(firstPredicate.equals(firstPredicate)); + + // same values -> returns true + TaskContainsKeywordsPredicate firstPredicateCopy = new TaskContainsKeywordsPredicate(firstPredicateKeywordList); + assertTrue(firstPredicate.equals(firstPredicateCopy)); + + // different types -> returns false + assertFalse(firstPredicate.equals(1)); + + // null -> returns false + assertFalse(firstPredicate.equals(null)); + + + // different person -> returns false + assertFalse(firstPredicate.equals(secondPredicate)); + } + + @Test + public void test_taskContainsKeywords_returnsTrue() { + // One keyword + TaskContainsKeywordsPredicate predicate = new TaskContainsKeywordsPredicate(Collections + .singletonList("exampleTask1")); + assertTrue(predicate.test(new TaskBuilder().withDescription("exampleTask1").buildPersonalTask())); + + // Multiple keywords + predicate = new TaskContainsKeywordsPredicate(Arrays.asList("exampleTask1", "exampleTask2")); + assertTrue(predicate.test(new TaskBuilder().withDescription("exampleTask1 exampleTask2").buildPersonalTask())); + + // Only one matching keyword + predicate = new TaskContainsKeywordsPredicate(Arrays.asList("exampleTask2", "exampleTask3")); + assertTrue(predicate.test(new TaskBuilder().withDescription("exampleTask1 exampleTask3").buildPersonalTask())); + + // Mixed-case keywords + predicate = new TaskContainsKeywordsPredicate(Arrays.asList("eXampleTask1", "ExampleTask2")); + assertTrue(predicate.test(new TaskBuilder().withDescription("exampleTask1 exampleTask2").buildPersonalTask())); + } +} + + diff --git a/src/test/java/seedu/address/model/UniqueTaskListTest.java b/src/test/java/seedu/address/model/UniqueTaskListTest.java new file mode 100644 index 000000000000..0f641ca7730e --- /dev/null +++ b/src/test/java/seedu/address/model/UniqueTaskListTest.java @@ -0,0 +1,90 @@ +package seedu.address.model; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.time.format.ResolverStyle; + +import org.junit.BeforeClass; +import org.junit.Rule; +import org.junit.Test; + +import org.junit.rules.ExpectedException; + +import seedu.address.model.personal.PersonalTask; +import seedu.address.model.task.exceptions.TimingClashException; +import seedu.address.model.tutee.TuitionTask; +import seedu.address.testutil.Assert; +import systemtests.SystemTestSetupHelper; + +//@@author ChoChihTun +public class UniqueTaskListTest { + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + private DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd/MM/uuuu HH:mm") + .withResolverStyle(ResolverStyle.STRICT); + private UniqueTaskList uniqueTaskList = new UniqueTaskList(); + + @BeforeClass + public static void setupBeforeClass() { + SystemTestSetupHelper.initialize(); + } + + @Test + public void asObservableList_modifyList_throwsUnsupportedOperationException() { + UniqueTaskList uniqueTaskList = new UniqueTaskList(); + thrown.expect(UnsupportedOperationException.class); + uniqueTaskList.asObservableList().remove(0); + } + + @Test + public void addNewTask_clashes_throwsTimingClashException() { + try { + createTaskList(); + } catch (TimingClashException e) { + throw new AssertionError("Should not have any clashed timing"); + } + // New task starts at the same time as an existing task + Assert.assertThrows(TimingClashException.class, () -> + uniqueTaskList.add(new PersonalTask( + LocalDateTime.parse("11/01/2011 22:00", formatter), "2h0m", "Homework 1"))); + + // New task starts during an existing task + Assert.assertThrows(TimingClashException.class, () -> + uniqueTaskList.add(new PersonalTask( + LocalDateTime.parse("15/01/2011 22:30", formatter), "2h0m", "Homework 2"))); + + // New task ends at the same time as an existing task + Assert.assertThrows(TimingClashException.class, () -> + uniqueTaskList.add(new PersonalTask( + LocalDateTime.parse("13/01/2011 11:30", formatter), "0h30m", "Homework 3"))); + + // New task ends during an existing task + Assert.assertThrows(TimingClashException.class, () -> + uniqueTaskList.add(new PersonalTask( + LocalDateTime.parse("13/01/2011 10:00", formatter), "1h30m", "Homework 4"))); + + // New task is within an existing task completely + Assert.assertThrows(TimingClashException.class, () -> + uniqueTaskList.add(new TuitionTask( + "Anne", LocalDateTime.parse("15/01/2011 22:30", formatter), "1h30m", "Assignment"))); + + // Existing task is within the new task completely + Assert.assertThrows(TimingClashException.class, () -> + uniqueTaskList.add(new TuitionTask( + "Ben", LocalDateTime.parse("11/01/2011 21:00", formatter), "4h0m", "Revision"))); + } + + /** + * Generates a list of existing tasks + */ + private void createTaskList() throws TimingClashException { + uniqueTaskList.add(new TuitionTask("Anne", + LocalDateTime.parse("11/01/2011 22:00", formatter), "1h30m", "tuition 1")); + uniqueTaskList.add(new PersonalTask( + LocalDateTime.parse("15/01/2011 22:00", formatter), "2h30m", "personal task 1")); + uniqueTaskList.add(new PersonalTask( + LocalDateTime.parse("13/01/2011 11:00", formatter), "1h0m", "personal task 2")); + } +} diff --git a/src/test/java/seedu/address/model/person/NameContainsKeywordsPredicateTest.java b/src/test/java/seedu/address/model/person/NameContainsKeywordsPredicateTest.java index 76841215e3a0..07827f83a1ca 100644 --- a/src/test/java/seedu/address/model/person/NameContainsKeywordsPredicateTest.java +++ b/src/test/java/seedu/address/model/person/NameContainsKeywordsPredicateTest.java @@ -34,6 +34,7 @@ public void equals() { // null -> returns false assertFalse(firstPredicate.equals(null)); + // different person -> returns false assertFalse(firstPredicate.equals(secondPredicate)); } @@ -67,9 +68,15 @@ public void test_nameDoesNotContainKeywords_returnsFalse() { predicate = new NameContainsKeywordsPredicate(Arrays.asList("Carol")); assertFalse(predicate.test(new PersonBuilder().withName("Alice Bob").build())); - // Keywords match phone, email and address, but does not match name - predicate = new NameContainsKeywordsPredicate(Arrays.asList("12345", "alice@email.com", "Main", "Street")); - assertFalse(predicate.test(new PersonBuilder().withName("Alice").withPhone("12345") - .withEmail("alice@email.com").withAddress("Main Street").build())); + // Keywords match email and address, but does not match phone + predicate = new NameContainsKeywordsPredicate(Arrays.asList("12354", "alice@email.com", "Main", "Street")); + assertFalse(predicate.test(new PersonBuilder().withPhone("85355255").withEmail("alice@email.com") + .withAddress("Main Street").build())); + + // Keywords match email and address, but does not match name + predicate = new NameContainsKeywordsPredicate(Arrays.asList("Bob", "alice@email.com", "Main", "Street")); + assertFalse(predicate.test(new PersonBuilder().withName("Alice").withEmail("alice@email.com") + .withAddress("Main Street").build())); + } } diff --git a/src/test/java/seedu/address/model/person/PersonSortUtilTest.java b/src/test/java/seedu/address/model/person/PersonSortUtilTest.java new file mode 100644 index 000000000000..51856b35c609 --- /dev/null +++ b/src/test/java/seedu/address/model/person/PersonSortUtilTest.java @@ -0,0 +1,238 @@ +package seedu.address.model.person; + +import static org.junit.Assert.assertEquals; +import static seedu.address.model.person.PersonSortUtil.CATEGORY_EDUCATION_LEVEL; +import static seedu.address.model.person.PersonSortUtil.CATEGORY_GRADE; +import static seedu.address.model.person.PersonSortUtil.CATEGORY_NAME; +import static seedu.address.model.person.PersonSortUtil.CATEGORY_SCHOOL; +import static seedu.address.model.person.PersonSortUtil.CATEGORY_SUBJECT; +import static seedu.address.model.person.PersonSortUtil.compareNameLexicographically; +import static seedu.address.model.person.PersonSortUtil.getComparator; + +import java.util.Comparator; + +import org.junit.Rule; +import org.junit.Test; + +import org.junit.rules.ExpectedException; + +import seedu.address.model.tutee.Tutee; + +import seedu.address.testutil.PersonBuilder; +import seedu.address.testutil.TuteeBuilder; + +//@@author yungyung04 +public class PersonSortUtilTest { + private static final String LOWER_ORDER = "a"; + private static final String MIDDLE_ORDER = "b"; + private static final String HIGHER_ORDER = "c"; + private static final String EDUCATION_LEVEL_PRIMARY = "primary"; + private static final String EDUCATION_LEVEL_SECONDARY = "secondary"; + private static final String EDUCATION_LEVEL_JUNIOR_COLLEGE = "junior college"; + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + private Person lowerOrder = new TuteeBuilder().withName(MIDDLE_ORDER).withEducationLevel(EDUCATION_LEVEL_PRIMARY) + .withGrade(MIDDLE_ORDER).withSchool(MIDDLE_ORDER).withSubject(MIDDLE_ORDER).build(); + private Person higherOrder = new TuteeBuilder().withName(HIGHER_ORDER).withEducationLevel(EDUCATION_LEVEL_SECONDARY) + .withGrade(HIGHER_ORDER).withSchool(HIGHER_ORDER).withSubject(HIGHER_ORDER).build(); + private Person versatileOrder; + + @Test + public void getComparator_validNameCategory_compareSuccessfully() { + //all first person's categories have lower lexicographical order + Comparator comparator = getComparator(CATEGORY_NAME); + int expected = lowerOrder.getName().fullName.compareTo(higherOrder.getName().fullName); + assertCompareSuccessfully(comparator, expected, lowerOrder, higherOrder); + + //first person's name has lower lexicographical order and the other categories have equal order + versatileOrder = new TuteeBuilder((Tutee) lowerOrder).withName(HIGHER_ORDER).build(); + expected = lowerOrder.getName().fullName.compareTo(versatileOrder.getName().fullName); + assertCompareSuccessfully(comparator, expected, lowerOrder, versatileOrder); + + //first person's name has lower lexicographical order but the other categories have higher order + versatileOrder = new TuteeBuilder((Tutee) higherOrder).withEducationLevel(EDUCATION_LEVEL_JUNIOR_COLLEGE) + .withGrade(LOWER_ORDER).withSchool(LOWER_ORDER).withSubject(LOWER_ORDER).build(); + expected = lowerOrder.getName().fullName.compareTo(versatileOrder.getName().fullName); + assertCompareSuccessfully(comparator, expected, lowerOrder, versatileOrder); + + //first person's name has equal lexicographical order + expected = lowerOrder.getName().fullName.compareTo(lowerOrder.getName().fullName); + assertCompareSuccessfully(comparator, expected, lowerOrder, lowerOrder); + + //first person's name has higher lexicographical order + expected = higherOrder.getName().fullName.compareTo(lowerOrder.getName().fullName); + assertCompareSuccessfully(comparator, expected, higherOrder, lowerOrder); + } + + @Test + public void getComparator_validEducationLevelCategory_compareSuccessfully() { + //all first person's categories have lower lexicographical order + Comparator comparator = getComparator(CATEGORY_EDUCATION_LEVEL); + int expected = ((Tutee) lowerOrder).getEducationLevel().toString() + .compareTo(((Tutee) higherOrder).getEducationLevel().toString()); + assertCompareSuccessfully(comparator, expected, lowerOrder, higherOrder); + + //first person's education level has lower lexicographical order and the other categories have equal order + versatileOrder = new TuteeBuilder((Tutee) lowerOrder).withEducationLevel(EDUCATION_LEVEL_SECONDARY).build(); + expected = ((Tutee) lowerOrder).getEducationLevel().toString() + .compareTo(((Tutee) versatileOrder).getEducationLevel().toString()); + assertCompareSuccessfully(comparator, expected, lowerOrder, versatileOrder); + + //first person's education level has lower lexicographical order but the other categories have higher order + versatileOrder = new TuteeBuilder((Tutee) higherOrder).withName(LOWER_ORDER) + .withGrade(LOWER_ORDER).withSchool(LOWER_ORDER).withSubject(LOWER_ORDER).build(); + expected = ((Tutee) lowerOrder).getEducationLevel().toString() + .compareTo(((Tutee) versatileOrder).getEducationLevel().toString()); + assertCompareSuccessfully(comparator, expected, lowerOrder, versatileOrder); + + //first person's education level has equal lexicographical order + expected = ((Tutee) lowerOrder).getEducationLevel().toString() + .compareTo(((Tutee) lowerOrder).getEducationLevel().toString()); + assertCompareSuccessfully(comparator, expected, lowerOrder, lowerOrder); + + //first person's education level has higher lexicographical order + expected = ((Tutee) higherOrder).getEducationLevel().toString() + .compareTo(((Tutee) lowerOrder).getEducationLevel().toString()); + assertCompareSuccessfully(comparator, expected, higherOrder, lowerOrder); + } + + @Test + public void getComparator_validGradeCategory_compareSuccessfully() { + //all first person's categories have lower lexicographical order + Comparator comparator = getComparator(CATEGORY_GRADE); + int expected = ((Tutee) lowerOrder).getGrade().toString() + .compareTo(((Tutee) higherOrder).getGrade().toString()); + assertCompareSuccessfully(comparator, expected, lowerOrder, higherOrder); + + //first person's grade has lower lexicographical order and the other categories have equal order + versatileOrder = new TuteeBuilder((Tutee) lowerOrder).withGrade(HIGHER_ORDER).build(); + expected = ((Tutee) lowerOrder).getGrade().toString() + .compareTo(((Tutee) versatileOrder).getGrade().toString()); + assertCompareSuccessfully(comparator, expected, lowerOrder, versatileOrder); + + //first person's grade has lower lexicographical order but the other categories have higher order + versatileOrder = new TuteeBuilder((Tutee) higherOrder).withEducationLevel(EDUCATION_LEVEL_JUNIOR_COLLEGE) + .withName(LOWER_ORDER).withSchool(LOWER_ORDER).withSubject(LOWER_ORDER).build(); + expected = ((Tutee) lowerOrder).getGrade().toString() + .compareTo(((Tutee) versatileOrder).getGrade().toString()); + assertCompareSuccessfully(comparator, expected, lowerOrder, versatileOrder); + + //first person's grade has equal lexicographical order + expected = ((Tutee) lowerOrder).getGrade().toString() + .compareTo(((Tutee) lowerOrder).getGrade().toString()); + assertCompareSuccessfully(comparator, expected, lowerOrder, lowerOrder); + + //first person's grade has higher lexicographical order + expected = ((Tutee) higherOrder).getGrade().toString() + .compareTo(((Tutee) lowerOrder).getGrade().toString()); + assertCompareSuccessfully(comparator, expected, higherOrder, lowerOrder); + } + + @Test + public void getComparator_validSchoolCategory_compareSuccessfully() { + //all first person's categories have lower lexicographical order + Comparator comparator = getComparator(CATEGORY_SCHOOL); + int expected = ((Tutee) lowerOrder).getSchool().toString() + .compareTo(((Tutee) higherOrder).getSchool().toString()); + assertCompareSuccessfully(comparator, expected, lowerOrder, higherOrder); + + //first person's school has lower lexicographical order and the other categories have equal order + versatileOrder = new TuteeBuilder((Tutee) lowerOrder).withSchool(HIGHER_ORDER).build(); + expected = ((Tutee) lowerOrder).getSchool().toString() + .compareTo(((Tutee) versatileOrder).getSchool().toString()); + assertCompareSuccessfully(comparator, expected, lowerOrder, versatileOrder); + + //first person's school has lower lexicographical order but the other categories have higher order + versatileOrder = new TuteeBuilder((Tutee) higherOrder).withEducationLevel(EDUCATION_LEVEL_JUNIOR_COLLEGE) + .withName(LOWER_ORDER).withGrade(LOWER_ORDER).withSubject(LOWER_ORDER).build(); + expected = ((Tutee) lowerOrder).getSchool().toString() + .compareTo(((Tutee) versatileOrder).getSchool().toString()); + assertCompareSuccessfully(comparator, expected, lowerOrder, versatileOrder); + + //first person's school has equal lexicographical order + expected = ((Tutee) lowerOrder).getSchool().toString() + .compareTo(((Tutee) lowerOrder).getSchool().toString()); + assertCompareSuccessfully(comparator, expected, lowerOrder, lowerOrder); + + //first person's school has higher lexicographical order + expected = ((Tutee) higherOrder).getSchool().toString() + .compareTo(((Tutee) lowerOrder).getSchool().toString()); + assertCompareSuccessfully(comparator, expected, higherOrder, lowerOrder); + } + + @Test + public void getComparator_validSubjectCategory_compareSuccessfully() { + //all first person's categories have lower lexicographical order + Comparator comparator = getComparator(CATEGORY_SUBJECT); + int expected = ((Tutee) lowerOrder).getSubject().toString() + .compareTo(((Tutee) higherOrder).getSubject().toString()); + assertCompareSuccessfully(comparator, expected, lowerOrder, higherOrder); + + //first person's subject has lower lexicographical order and the other categories have equal order + versatileOrder = new TuteeBuilder((Tutee) lowerOrder).withSubject(HIGHER_ORDER).build(); + expected = ((Tutee) lowerOrder).getSubject().toString() + .compareTo(((Tutee) versatileOrder).getSubject().toString()); + assertCompareSuccessfully(comparator, expected, lowerOrder, versatileOrder); + + //first person's subject has lower lexicographical order but the other categories have higher order + versatileOrder = new TuteeBuilder((Tutee) higherOrder).withEducationLevel(EDUCATION_LEVEL_JUNIOR_COLLEGE) + .withName(LOWER_ORDER).withGrade(LOWER_ORDER).withSchool(LOWER_ORDER).build(); + expected = ((Tutee) lowerOrder).getSubject().toString() + .compareTo(((Tutee) versatileOrder).getSubject().toString()); + assertCompareSuccessfully(comparator, expected, lowerOrder, versatileOrder); + + //first person's subject has equal lexicographical order + expected = ((Tutee) lowerOrder).getSubject().toString() + .compareTo(((Tutee) lowerOrder).getSubject().toString()); + assertCompareSuccessfully(comparator, expected, lowerOrder, lowerOrder); + + //first person's subject has higher lexicographical order + expected = ((Tutee) higherOrder).getSubject().toString() + .compareTo(((Tutee) lowerOrder).getSubject().toString()); + assertCompareSuccessfully(comparator, expected, higherOrder, lowerOrder); + } + + @Test + public void getComparator_invalidCategory_assertionErrorHappen() { + thrown.expect(AssertionError.class); + Comparator comparator = getComparator("email"); + } + + @Test + public void compareNameLexicographically_validInput_compareSuccessfully() { + lowerOrder = new PersonBuilder().withName("Albert").build(); + higherOrder = new PersonBuilder().withName("Alice").build(); + + //first person has lower lexicographical order + int expected = PersonSortUtil.compareNameLexicographically(lowerOrder, higherOrder); + int actual = lowerOrder.getName().fullName.compareToIgnoreCase(higherOrder.getName().fullName); + assertEquals(expected, actual); + + //first person has higher lexicographical order + expected = PersonSortUtil.compareNameLexicographically(higherOrder, lowerOrder); + actual = higherOrder.getName().fullName.compareToIgnoreCase(lowerOrder.getName().fullName); + assertEquals(expected, actual); + + //both have exactly same name + Person lowerOrderCopy = new PersonBuilder(lowerOrder).build(); + expected = PersonSortUtil.compareNameLexicographically(lowerOrder, lowerOrderCopy); + actual = lowerOrder.getName().fullName.compareToIgnoreCase(lowerOrderCopy.getName().fullName); + assertEquals(expected, actual); + + //both have same name with different cases -> treated as 2 same namess + higherOrder = new PersonBuilder(lowerOrder).withName("ALBERT").build(); + int expectedFromSameName = expected; + int expectedFromDiffName = compareNameLexicographically(lowerOrder, higherOrder); + assertEquals(expectedFromSameName, expectedFromDiffName); + } + + /** + * Checks whether comparator is able to perform the desired comparison. + */ + private void assertCompareSuccessfully(Comparator comparator, int expected, Person first, Person second) { + int actual = comparator.compare(first, second); + assertEquals(expected, actual); + } +} diff --git a/src/test/java/seedu/address/model/personal/PersonalTaskTest.java b/src/test/java/seedu/address/model/personal/PersonalTaskTest.java new file mode 100644 index 000000000000..a8bc60e5595a --- /dev/null +++ b/src/test/java/seedu/address/model/personal/PersonalTaskTest.java @@ -0,0 +1,40 @@ +package seedu.address.model.personal; + +import static org.junit.Assert.assertEquals; +import static seedu.address.testutil.TypicalCalendarEntries.VALID_DESCRIPTION; +import static seedu.address.testutil.TypicalCalendarEntries.VALID_DURATION; +import static seedu.address.testutil.TypicalCalendarEntries.VALID_START_DATE_TIME; +import static seedu.address.testutil.TypicalCalendarEntries.getPersonalEntry; + +import org.junit.BeforeClass; +import org.junit.Test; + +import com.calendarfx.model.Entry; + +import seedu.address.testutil.TypicalCalendarEntries; + +//@@author ChoChihTun +public class PersonalTaskTest { + + @BeforeClass + public static void setupBeforeClass() { + new TypicalCalendarEntries(); + } + + @Test + public void constructor_validArgs_success() { + PersonalTask personalTask = new PersonalTask(VALID_START_DATE_TIME, + VALID_DURATION, VALID_DESCRIPTION); + Entry actualEntry = personalTask.getEntry(); + Entry expectedEntry = getPersonalEntry(); + + // To match the ID of the same entry + actualEntry.setId("0"); + expectedEntry.setId("0"); + + assertEquals(VALID_START_DATE_TIME, personalTask.getTaskDateTime()); + assertEquals(VALID_DURATION, personalTask.getDuration()); + assertEquals(VALID_DESCRIPTION, personalTask.getDescription()); + assertEquals(expectedEntry, actualEntry); + } +} diff --git a/src/test/java/seedu/address/model/tutee/EducationLevelContainsKeywordsPredicateTest.java b/src/test/java/seedu/address/model/tutee/EducationLevelContainsKeywordsPredicateTest.java new file mode 100644 index 000000000000..7b194827a9cd --- /dev/null +++ b/src/test/java/seedu/address/model/tutee/EducationLevelContainsKeywordsPredicateTest.java @@ -0,0 +1,93 @@ +package seedu.address.model.tutee; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static seedu.address.logic.commands.CommandTestUtil.VALID_EDUCATION_LEVEL_AMY; +import static seedu.address.logic.commands.CommandTestUtil.VALID_EDUCATION_LEVEL_ROBERT; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import org.junit.Test; + +import seedu.address.testutil.TuteeBuilder; + +//@@author yungyung04 +public class EducationLevelContainsKeywordsPredicateTest { + + @Test + public void equals() { + List firstPredicateKeywordList = Collections.singletonList("first"); + List secondPredicateKeywordList = Arrays.asList("first", "second"); + + EducationLevelContainsKeywordsPredicate firstPredicate = + new EducationLevelContainsKeywordsPredicate(firstPredicateKeywordList); + EducationLevelContainsKeywordsPredicate secondPredicate = + new EducationLevelContainsKeywordsPredicate(secondPredicateKeywordList); + + // same object -> returns true + assertTrue(firstPredicate.equals(firstPredicate)); + + // same values -> returns true + EducationLevelContainsKeywordsPredicate firstPredicateCopy = + new EducationLevelContainsKeywordsPredicate(firstPredicateKeywordList); + assertTrue(firstPredicate.equals(firstPredicateCopy)); + + // different types -> returns false + assertFalse(firstPredicate.equals(1)); + + // null -> returns false + assertFalse(firstPredicate.equals(null)); + + // different education levels -> returns false + assertFalse(firstPredicate.equals(secondPredicate)); + } + + @Test + public void test_educationLevelContainsKeywords_returnsTrue() { + // One keyword + EducationLevelContainsKeywordsPredicate predicate = + new EducationLevelContainsKeywordsPredicate(Collections.singletonList(VALID_EDUCATION_LEVEL_AMY)); + assertTrue(predicate.test(new TuteeBuilder().withEducationLevel(VALID_EDUCATION_LEVEL_AMY).build())); + + // Multiple keywords + predicate = new EducationLevelContainsKeywordsPredicate(Arrays + .asList("junior", "college")); + assertTrue(predicate.test(new TuteeBuilder() + .withEducationLevel("junior college").build())); + + // Only one matching keyword + predicate = new EducationLevelContainsKeywordsPredicate(Arrays.asList("junior")); + assertTrue(predicate.test(new TuteeBuilder() + .withEducationLevel("junior college").build())); + + // Mixed-case keywords + predicate = new EducationLevelContainsKeywordsPredicate(Arrays.asList("JuNiOr", "colLEGE")); + assertTrue(predicate.test(new TuteeBuilder().withEducationLevel("junior college").build())); + } + + @Test + public void test_educationLevelDoesNotContainKeywords_returnsFalse() { + // Zero keywords + EducationLevelContainsKeywordsPredicate predicate = + new EducationLevelContainsKeywordsPredicate(Collections.emptyList()); + assertFalse(predicate.test(new TuteeBuilder().withEducationLevel(VALID_EDUCATION_LEVEL_AMY).build())); + + // Non-matching keyword + predicate = new EducationLevelContainsKeywordsPredicate(Arrays.asList(VALID_EDUCATION_LEVEL_ROBERT)); + assertFalse(predicate.test(new TuteeBuilder().withEducationLevel(VALID_EDUCATION_LEVEL_AMY).build())); + + // Keywords match grade, school and subject, but does not match education level + predicate = new EducationLevelContainsKeywordsPredicate(Arrays.asList("school", "B", "mathematics")); + assertFalse(predicate.test(new TuteeBuilder().withEducationLevel(VALID_EDUCATION_LEVEL_AMY).withSchool("school") + .withGrade("B").withSubject("mathematics").build())); + + // Keywords match email and address, but does not match education level + predicate = new EducationLevelContainsKeywordsPredicate(Arrays + .asList("Bob", "alice@email.com", "Main", "Street")); + assertFalse(predicate.test(new TuteeBuilder().withEducationLevel(VALID_EDUCATION_LEVEL_AMY) + .withEmail("alice@email.com").withAddress("Main Street").build())); + + } +} diff --git a/src/test/java/seedu/address/model/tutee/EducationLevelTest.java b/src/test/java/seedu/address/model/tutee/EducationLevelTest.java new file mode 100644 index 000000000000..de28e7b8ca68 --- /dev/null +++ b/src/test/java/seedu/address/model/tutee/EducationLevelTest.java @@ -0,0 +1,44 @@ +package seedu.address.model.tutee; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; + +import seedu.address.testutil.Assert; + +//@@author ChoChihTun +public class EducationLevelTest { + + @Test + public void constructor_null_throwsNullPointerException() { + Assert.assertThrows(NullPointerException.class, () -> new EducationLevel(null)); + } + + @Test + public void constructor_invalidEducationLevel_throwsIllegalArgumentException() { + String invalidEducationLevel = ""; + Assert.assertThrows(IllegalArgumentException.class, () -> new EducationLevel(invalidEducationLevel)); + } + + @Test + public void isValidEducationLevel() { + // null education level + Assert.assertThrows(NullPointerException.class, () -> EducationLevel.isValidEducationLevel(null)); + + // invalid education level + assertFalse(EducationLevel.isValidEducationLevel("")); // empty string + assertFalse(EducationLevel.isValidEducationLevel(" ")); // spaces only + assertFalse(EducationLevel.isValidEducationLevel("91")); // numbers + assertFalse(EducationLevel.isValidEducationLevel("university")); // not the specified education level + assertFalse(EducationLevel.isValidEducationLevel("primary5")); // contains number + assertFalse(EducationLevel.isValidEducationLevel("primary@")); // contains special characters + assertFalse(EducationLevel.isValidEducationLevel(" secondary ")); // multiple leading and trailing whitespaces + + // valid education level + assertTrue(EducationLevel.isValidEducationLevel("primary")); // primary school + assertTrue(EducationLevel.isValidEducationLevel("secondary")); // secondary school + assertTrue(EducationLevel.isValidEducationLevel("junior college")); // junior college + assertTrue(EducationLevel.isValidEducationLevel("SeCoNdaRy")); // Capital + } +} diff --git a/src/test/java/seedu/address/model/tutee/GradeContainsKeywordsPredicateTest.java b/src/test/java/seedu/address/model/tutee/GradeContainsKeywordsPredicateTest.java new file mode 100644 index 000000000000..2ed80d9397c2 --- /dev/null +++ b/src/test/java/seedu/address/model/tutee/GradeContainsKeywordsPredicateTest.java @@ -0,0 +1,87 @@ +package seedu.address.model.tutee; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static seedu.address.logic.commands.CommandTestUtil.VALID_GRADE_AMY; +import static seedu.address.logic.commands.CommandTestUtil.VALID_GRADE_BOB; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import org.junit.Test; + +import seedu.address.testutil.TuteeBuilder; + +//@@author yungyung04 +public class GradeContainsKeywordsPredicateTest { + + @Test + public void equals() { + List firstPredicateKeywordList = Collections.singletonList("first"); + List secondPredicateKeywordList = Arrays.asList("first", "second"); + + GradeContainsKeywordsPredicate firstPredicate = + new GradeContainsKeywordsPredicate(firstPredicateKeywordList); + GradeContainsKeywordsPredicate secondPredicate = + new GradeContainsKeywordsPredicate(secondPredicateKeywordList); + + // same object -> returns true + assertTrue(firstPredicate.equals(firstPredicate)); + + // same values -> returns true + GradeContainsKeywordsPredicate firstPredicateCopy = + new GradeContainsKeywordsPredicate(firstPredicateKeywordList); + assertTrue(firstPredicate.equals(firstPredicateCopy)); + + // different types -> returns false + assertFalse(firstPredicate.equals(1)); + + // null -> returns false + assertFalse(firstPredicate.equals(null)); + + // different education levels -> returns false + assertFalse(firstPredicate.equals(secondPredicate)); + } + + @Test + public void test_gradeContainsKeywords_returnsTrue() { + // One keyword + GradeContainsKeywordsPredicate predicate = + new GradeContainsKeywordsPredicate(Collections.singletonList(VALID_GRADE_AMY)); + assertTrue(predicate.test(new TuteeBuilder().withGrade(VALID_GRADE_AMY).build())); + + // Only one matching keyword + predicate = new GradeContainsKeywordsPredicate(Arrays.asList(VALID_GRADE_AMY, VALID_GRADE_BOB)); + assertTrue(predicate.test(new TuteeBuilder() + .withGrade(VALID_GRADE_AMY).build())); + + // Mixed-case keywords + predicate = new GradeContainsKeywordsPredicate(Arrays.asList("a")); + assertTrue(predicate.test(new TuteeBuilder().withGrade("A").build())); + } + + @Test + public void test_gradeDoesNotContainKeywords_returnsFalse() { + // Zero keywords + GradeContainsKeywordsPredicate predicate = + new GradeContainsKeywordsPredicate(Collections.emptyList()); + assertFalse(predicate.test(new TuteeBuilder().withGrade(VALID_GRADE_AMY).build())); + + // Non-matching keyword + predicate = new GradeContainsKeywordsPredicate(Arrays.asList(VALID_GRADE_BOB)); + assertFalse(predicate.test(new TuteeBuilder().withGrade(VALID_GRADE_AMY).build())); + + // Keywords match education level, school and subject, but does not match grade + predicate = new GradeContainsKeywordsPredicate(Arrays.asList("school", "primary", "mathematics")); + assertFalse(predicate.test(new TuteeBuilder().withGrade(VALID_GRADE_AMY).withSchool("school") + .withEducationLevel("primary").withSubject("mathematics").build())); + + // Keywords match email and address, but does not match grade + predicate = new GradeContainsKeywordsPredicate(Arrays + .asList(VALID_GRADE_BOB, "alice@email.com", "Main", "Street")); + assertFalse(predicate.test(new TuteeBuilder().withGrade(VALID_GRADE_AMY) + .withEmail("alice@email.com").withAddress("Main Street").build())); + + } +} diff --git a/src/test/java/seedu/address/model/tutee/GradeTest.java b/src/test/java/seedu/address/model/tutee/GradeTest.java new file mode 100644 index 000000000000..6e65475b0235 --- /dev/null +++ b/src/test/java/seedu/address/model/tutee/GradeTest.java @@ -0,0 +1,45 @@ +package seedu.address.model.tutee; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; + +import seedu.address.testutil.Assert; + +//@@author ChoChihTun +public class GradeTest { + + @Test + public void constructor_null_throwsNullPointerException() { + Assert.assertThrows(NullPointerException.class, () -> new Grade(null)); + } + + @Test + public void constructor_invalidGrade_throwsIllegalArgumentException() { + String invalidGrade = ""; + Assert.assertThrows(IllegalArgumentException.class, () -> new Grade(invalidGrade)); + } + + @Test + public void isValidGrade() { + // null grade + Assert.assertThrows(NullPointerException.class, () -> Grade.isValidGrade(null)); + + // invalid grade + assertFalse(Grade.isValidGrade("")); // empty string + assertFalse(Grade.isValidGrade(" ")); // spaces only + assertFalse(Grade.isValidGrade("9112")); // only contains numbers + assertFalse(Grade.isValidGrade("pass")); // more than 2 alphabet + assertFalse(Grade.isValidGrade("+B")); // special character before alphabet + assertFalse(Grade.isValidGrade("B -")); // spaces within digits + assertFalse(Grade.isValidGrade(" B")); // leading whitespace + + // valid grade + assertTrue(Grade.isValidGrade("A+")); // 1 alphabet followed by a special character + assertTrue(Grade.isValidGrade("B")); // only 1 alphabet + assertTrue(Grade.isValidGrade("b")); // small letter + assertTrue(Grade.isValidGrade("C5")); // number after alphabet + } + +} diff --git a/src/test/java/seedu/address/model/tutee/SchoolContainsKeywordsPredicateTest.java b/src/test/java/seedu/address/model/tutee/SchoolContainsKeywordsPredicateTest.java new file mode 100644 index 000000000000..ba1c942d0a8d --- /dev/null +++ b/src/test/java/seedu/address/model/tutee/SchoolContainsKeywordsPredicateTest.java @@ -0,0 +1,86 @@ +package seedu.address.model.person; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static seedu.address.logic.commands.CommandTestUtil.VALID_SCHOOL_AMY; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import org.junit.Test; + +import seedu.address.model.tutee.SchoolContainsKeywordsPredicate; +import seedu.address.testutil.TuteeBuilder; + +//@@author yungyung04 +public class SchoolContainsKeywordsPredicateTest { + + @Test + public void equals() { + List firstPredicateKeywordList = Collections.singletonList("first"); + List secondPredicateKeywordList = Arrays.asList("first", "second"); + + SchoolContainsKeywordsPredicate firstPredicate = + new SchoolContainsKeywordsPredicate(firstPredicateKeywordList); + SchoolContainsKeywordsPredicate secondPredicate = + new SchoolContainsKeywordsPredicate(secondPredicateKeywordList); + + // same object -> returns true + assertTrue(firstPredicate.equals(firstPredicate)); + + // same values -> returns true + SchoolContainsKeywordsPredicate firstPredicateCopy = + new SchoolContainsKeywordsPredicate(firstPredicateKeywordList); + assertTrue(firstPredicate.equals(firstPredicateCopy)); + + // different types -> returns false + assertFalse(firstPredicate.equals(1)); + + // null -> returns false + assertFalse(firstPredicate.equals(null)); + + // different schools -> returns false + assertFalse(firstPredicate.equals(secondPredicate)); + } + + @Test + public void test_schoolContainsKeywords_returnsTrue() { + // One keyword + SchoolContainsKeywordsPredicate predicate = + new SchoolContainsKeywordsPredicate(Collections.singletonList("nan")); + assertTrue(predicate.test(new TuteeBuilder().withSchool("nan hua high school").build())); + + // Only one matching keyword + predicate = new SchoolContainsKeywordsPredicate(Arrays.asList("nan", "victoria")); + assertTrue(predicate.test(new TuteeBuilder().withSchool("victoria").build())); + + // Mixed-case keywords + predicate = new SchoolContainsKeywordsPredicate(Arrays.asList("nan")); + assertTrue(predicate.test(new TuteeBuilder().withSchool("NAN").build())); + } + + @Test + public void test_schoolDoesNotContainKeywords_returnsFalse() { + // Zero keywords + SchoolContainsKeywordsPredicate predicate = + new SchoolContainsKeywordsPredicate(Collections.emptyList()); + assertFalse(predicate.test(new TuteeBuilder().withSchool(VALID_SCHOOL_AMY).build())); + + // Non-matching keyword + predicate = new SchoolContainsKeywordsPredicate(Arrays.asList("victoria")); + assertFalse(predicate.test(new TuteeBuilder().withSchool("nan hua high school").build())); + + // Keywords match education level, grade and subject, but does not match school + predicate = new SchoolContainsKeywordsPredicate(Arrays.asList("B", "primary", "mathematics")); + assertFalse(predicate.test(new TuteeBuilder().withSchool(VALID_SCHOOL_AMY).withGrade("B") + .withEducationLevel("primary").withSubject("mathematics").build())); + + // Keywords match email and address, but does not match school + predicate = new SchoolContainsKeywordsPredicate(Arrays + .asList("victoria", "alice@email.com", "Main", "Street")); + assertFalse(predicate.test(new TuteeBuilder().withSchool("nan hua high school") + .withEmail("alice@email.com").withAddress("Main Street").build())); + + } +} diff --git a/src/test/java/seedu/address/model/tutee/SchoolTest.java b/src/test/java/seedu/address/model/tutee/SchoolTest.java new file mode 100644 index 000000000000..01f09740112b --- /dev/null +++ b/src/test/java/seedu/address/model/tutee/SchoolTest.java @@ -0,0 +1,43 @@ +package seedu.address.model.tutee; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; + +import seedu.address.testutil.Assert; + +//@@author ChoChihTun +public class SchoolTest { + + @Test + public void constructor_null_throwsNullPointerException() { + Assert.assertThrows(NullPointerException.class, () -> new School(null)); + } + + @Test + public void constructor_invalidSchool_throwsIllegalArgumentException() { + String invalidSchool = ""; + Assert.assertThrows(IllegalArgumentException.class, () -> new School(invalidSchool)); + } + + @Test + public void isValidSchool() { + // null school name + Assert.assertThrows(NullPointerException.class, () -> School.isValidSchool(null)); + + // invalid school name + assertFalse(School.isValidSchool("")); // empty string + assertFalse(School.isValidSchool(" ")); // spaces only + assertFalse(School.isValidSchool("^")); // only non-alphabetic characters + assertFalse(School.isValidSchool("bedok primary school*")); // contains non-alphabetic characters + assertFalse(School.isValidSchool("911")); // numbers only + assertFalse(School.isValidSchool("bedok12 secondary school")); // contains numbers + + // valid school name + assertTrue(School.isValidSchool("victoria junior college")); // alphabets only + assertTrue(School.isValidSchool("Victoria Junior College")); // with capital letters + assertTrue(School.isValidSchool("The longest name school primary school")); // long school name + } + +} diff --git a/src/test/java/seedu/address/model/tutee/SubjectContainsKeywordsPredicateTest.java b/src/test/java/seedu/address/model/tutee/SubjectContainsKeywordsPredicateTest.java new file mode 100644 index 000000000000..07786f82a01c --- /dev/null +++ b/src/test/java/seedu/address/model/tutee/SubjectContainsKeywordsPredicateTest.java @@ -0,0 +1,87 @@ +package seedu.address.model.person; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static seedu.address.logic.commands.CommandTestUtil.VALID_SUBJECT_AMY; +import static seedu.address.logic.commands.CommandTestUtil.VALID_SUBJECT_BOB; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import org.junit.Test; + +import seedu.address.model.tutee.SubjectContainsKeywordsPredicate; +import seedu.address.testutil.TuteeBuilder; + +//@@author yungyung04 +public class SubjectContainsKeywordsPredicateTest { + + @Test + public void equals() { + List firstPredicateKeywordList = Collections.singletonList("first"); + List secondPredicateKeywordList = Arrays.asList("first", "second"); + + SubjectContainsKeywordsPredicate firstPredicate = + new SubjectContainsKeywordsPredicate(firstPredicateKeywordList); + SubjectContainsKeywordsPredicate secondPredicate = + new SubjectContainsKeywordsPredicate(secondPredicateKeywordList); + + // same object -> returns true + assertTrue(firstPredicate.equals(firstPredicate)); + + // same values -> returns true + SubjectContainsKeywordsPredicate firstPredicateCopy = + new SubjectContainsKeywordsPredicate(firstPredicateKeywordList); + assertTrue(firstPredicate.equals(firstPredicateCopy)); + + // different types -> returns false + assertFalse(firstPredicate.equals(1)); + + // null -> returns false + assertFalse(firstPredicate.equals(null)); + + // different subjects -> returns false + assertFalse(firstPredicate.equals(secondPredicate)); + } + + @Test + public void test_subjectContainsKeywords_returnsTrue() { + // One keyword + SubjectContainsKeywordsPredicate predicate = + new SubjectContainsKeywordsPredicate(Collections.singletonList(VALID_SUBJECT_AMY)); + assertTrue(predicate.test(new TuteeBuilder().withSubject(VALID_SUBJECT_AMY).build())); + + // Only one matching keyword + predicate = new SubjectContainsKeywordsPredicate(Arrays.asList(VALID_SUBJECT_AMY, VALID_SUBJECT_BOB)); + assertTrue(predicate.test(new TuteeBuilder().withSubject(VALID_SUBJECT_AMY).build())); + + // Mixed-case keywords + predicate = new SubjectContainsKeywordsPredicate(Arrays.asList("MatheMAtics")); + assertTrue(predicate.test(new TuteeBuilder().withSubject("mathematics").build())); + } + + @Test + public void test_subjectDoesNotContainKeywords_returnsFalse() { + // Zero keywords + SubjectContainsKeywordsPredicate predicate = + new SubjectContainsKeywordsPredicate(Collections.emptyList()); + assertFalse(predicate.test(new TuteeBuilder().withSubject(VALID_SUBJECT_AMY).build())); + + // Non-matching keyword + predicate = new SubjectContainsKeywordsPredicate(Arrays.asList(VALID_SUBJECT_BOB)); + assertFalse(predicate.test(new TuteeBuilder().withSubject(VALID_SUBJECT_AMY).build())); + + // Keywords match education level, grade and school, but does not match subject + predicate = new SubjectContainsKeywordsPredicate(Arrays.asList("B", "primary", "school")); + assertFalse(predicate.test(new TuteeBuilder().withSubject(VALID_SUBJECT_AMY).withGrade("B") + .withEducationLevel("primary").withSchool("school").build())); + + // Keywords match email and address, but does not match subject + predicate = new SubjectContainsKeywordsPredicate(Arrays + .asList(VALID_SUBJECT_BOB, "alice@email.com", "Main", "Street")); + assertFalse(predicate.test(new TuteeBuilder().withSubject(VALID_SUBJECT_AMY) + .withEmail("alice@email.com").withAddress("Main Street").build())); + + } +} diff --git a/src/test/java/seedu/address/model/tutee/SubjectTest.java b/src/test/java/seedu/address/model/tutee/SubjectTest.java new file mode 100644 index 000000000000..05a9008ef5e1 --- /dev/null +++ b/src/test/java/seedu/address/model/tutee/SubjectTest.java @@ -0,0 +1,43 @@ +package seedu.address.model.tutee; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; + +import seedu.address.testutil.Assert; + +//@@author ChoChihTun +public class SubjectTest { + + @Test + public void constructor_null_throwsNullPointerException() { + Assert.assertThrows(NullPointerException.class, () -> new Subject(null)); + } + + @Test + public void constructor_invalidSubject_throwsIllegalArgumentException() { + String invalidSubject = ""; + Assert.assertThrows(IllegalArgumentException.class, () -> new Subject(invalidSubject)); + } + + @Test + public void isValidSubject() { + // null subject + Assert.assertThrows(NullPointerException.class, () -> Subject.isValidSubject(null)); + + // invalid subject name + assertFalse(Subject.isValidSubject("")); // empty string + assertFalse(Subject.isValidSubject(" ")); // spaces only + assertFalse(Subject.isValidSubject("^")); // only non-alphabetic characters + assertFalse(Subject.isValidSubject("economics*")); // contains non-alphabetic characters + assertFalse(Subject.isValidSubject("911")); // numbers only + assertFalse(Subject.isValidSubject("math12")); // contains numbers + + // valid subject name + assertTrue(Subject.isValidSubject("social studies")); // alphabets only + assertTrue(Subject.isValidSubject("Social Studies")); // with capital letters + assertTrue(Subject.isValidSubject("introduction to fluid dynamics")); // long subject name + } + +} diff --git a/src/test/java/seedu/address/model/tutee/TuitionTaskTest.java b/src/test/java/seedu/address/model/tutee/TuitionTaskTest.java new file mode 100644 index 000000000000..9d1031bbb397 --- /dev/null +++ b/src/test/java/seedu/address/model/tutee/TuitionTaskTest.java @@ -0,0 +1,42 @@ +package seedu.address.model.tutee; + +import static org.junit.Assert.assertEquals; +import static seedu.address.testutil.TypicalCalendarEntries.VALID_DESCRIPTION; +import static seedu.address.testutil.TypicalCalendarEntries.VALID_DURATION; +import static seedu.address.testutil.TypicalCalendarEntries.VALID_NAME; +import static seedu.address.testutil.TypicalCalendarEntries.VALID_START_DATE_TIME; +import static seedu.address.testutil.TypicalCalendarEntries.getTuitionEntry; + +import org.junit.BeforeClass; +import org.junit.Test; + +import com.calendarfx.model.Entry; + +import seedu.address.testutil.TypicalCalendarEntries; + +//@@author ChoChihTun +public class TuitionTaskTest { + + @BeforeClass + public static void setupBeforeClass() { + new TypicalCalendarEntries(); + } + + @Test + public void constructor_validArgs_success() { + TuitionTask tuitionTask = new TuitionTask(VALID_NAME, VALID_START_DATE_TIME, + VALID_DURATION, VALID_DESCRIPTION); + Entry actualEntry = tuitionTask.getEntry(); + Entry expectedEntry = getTuitionEntry(); + + // To match the ID of the same entry + actualEntry.setId("0"); + expectedEntry.setId("0"); + + assertEquals(VALID_NAME, tuitionTask.getPerson()); + assertEquals(VALID_START_DATE_TIME, tuitionTask.getTaskDateTime()); + assertEquals(VALID_DURATION, tuitionTask.getDuration()); + assertEquals(VALID_DESCRIPTION, tuitionTask.getDescription()); + assertEquals(expectedEntry, actualEntry); + } +} diff --git a/src/test/java/seedu/address/storage/StorageManagerTest.java b/src/test/java/seedu/address/storage/StorageManagerTest.java index 419021d459f6..886e667e04ca 100644 --- a/src/test/java/seedu/address/storage/StorageManagerTest.java +++ b/src/test/java/seedu/address/storage/StorageManagerTest.java @@ -3,7 +3,7 @@ import static junit.framework.TestCase.assertNotNull; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; -import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook; +import static seedu.address.testutil.typicaladdressbook.TypicalAddressBookCompiler.getTypicalAddressBook1; import java.io.IOException; @@ -61,7 +61,7 @@ public void addressBookReadSave() throws Exception { * {@link XmlAddressBookStorage} class. * More extensive testing of UserPref saving/reading is done in {@link XmlAddressBookStorageTest} class. */ - AddressBook original = getTypicalAddressBook(); + AddressBook original = getTypicalAddressBook1(); storageManager.saveAddressBook(original); ReadOnlyAddressBook retrieved = storageManager.readAddressBook().get(); assertEquals(original, new AddressBook(retrieved)); diff --git a/src/test/java/seedu/address/storage/XmlAdaptedPersonTest.java b/src/test/java/seedu/address/storage/XmlAdaptedPersonTest.java index c3c91a5c27a7..32952c109d58 100644 --- a/src/test/java/seedu/address/storage/XmlAdaptedPersonTest.java +++ b/src/test/java/seedu/address/storage/XmlAdaptedPersonTest.java @@ -2,7 +2,8 @@ import static org.junit.Assert.assertEquals; import static seedu.address.storage.XmlAdaptedPerson.MISSING_FIELD_MESSAGE_FORMAT; -import static seedu.address.testutil.TypicalPersons.BENSON; +import static seedu.address.testutil.typicaladdressbook.TypicalPersons.BENSON; +import static seedu.address.testutil.typicaladdressbook.TypicalTutees.ALICETUTEE; import java.util.ArrayList; import java.util.List; @@ -15,6 +16,10 @@ import seedu.address.model.person.Email; import seedu.address.model.person.Name; import seedu.address.model.person.Phone; +import seedu.address.model.tutee.EducationLevel; +import seedu.address.model.tutee.Grade; +import seedu.address.model.tutee.School; +import seedu.address.model.tutee.Subject; import seedu.address.testutil.Assert; public class XmlAdaptedPersonTest { @@ -23,6 +28,10 @@ public class XmlAdaptedPersonTest { private static final String INVALID_ADDRESS = " "; private static final String INVALID_EMAIL = "example.com"; private static final String INVALID_TAG = "#friend"; + private static final String INVALID_SUBJECT = "1201"; + private static final String INVALID_GRADE = "100"; + private static final String INVALID_EDUCATION_LEVEL = "8th grade"; + private static final String INVALID_SCHOOL = " "; private static final String VALID_NAME = BENSON.getName().toString(); private static final String VALID_PHONE = BENSON.getPhone().toString(); @@ -32,6 +41,18 @@ public class XmlAdaptedPersonTest { .map(XmlAdaptedTag::new) .collect(Collectors.toList()); + private static final String VALID_TUTEE_NAME = ALICETUTEE.getName().toString(); + private static final String VALID_TUTEE_PHONE = ALICETUTEE.getPhone().toString(); + private static final String VALID_TUTEE_EMAIL = ALICETUTEE.getEmail().toString(); + private static final String VALID_TUTEE_ADDRESS = ALICETUTEE.getAddress().toString(); + private static final String VALID_TUTEE_SUBJECT = ALICETUTEE.getSubject().toString(); + private static final String VALID_TUTEE_GRADE = ALICETUTEE.getGrade().toString(); + private static final String VALID_TUTEE_EDUCATION_LEVEL = ALICETUTEE.getEducationLevel().toString(); + private static final String VALID_TUTEE_SCHOOL = ALICETUTEE.getSchool().toString(); + private static final List VALID_TUTEE_TAGS = ALICETUTEE.getTags().stream() + .map(XmlAdaptedTag::new) + .collect(Collectors.toList()); + @Test public void toModelType_validPersonDetails_returnsPerson() throws Exception { XmlAdaptedPerson person = new XmlAdaptedPerson(BENSON); @@ -107,4 +128,183 @@ public void toModelType_invalidTags_throwsIllegalValueException() { Assert.assertThrows(IllegalValueException.class, person::toModelType); } + //@@author yungyung04 + //=========== Tutee Related Tests ============================================================= + + @Test + public void toModelType_validTuteeDetails_returnsTutee() throws Exception { + XmlAdaptedPerson tutee = new XmlAdaptedPerson(ALICETUTEE); + assertEquals(ALICETUTEE, tutee.toModelType()); + } + + @Test + public void toModelType_invalidTuteeName_throwsIllegalValueException() { + XmlAdaptedPerson person = + new XmlAdaptedPerson(INVALID_NAME, VALID_TUTEE_PHONE, VALID_TUTEE_EMAIL, VALID_TUTEE_ADDRESS, + VALID_TUTEE_SUBJECT, VALID_TUTEE_GRADE, VALID_TUTEE_EDUCATION_LEVEL, + VALID_TUTEE_SCHOOL, VALID_TUTEE_TAGS); + String expectedMessage = Name.MESSAGE_NAME_CONSTRAINTS; + Assert.assertThrows(IllegalValueException.class, expectedMessage, person::toModelType); + } + + @Test + public void toModelType_nullTuteeName_throwsIllegalValueException() { + XmlAdaptedPerson person = + new XmlAdaptedPerson(null, VALID_TUTEE_PHONE, VALID_TUTEE_EMAIL, VALID_TUTEE_ADDRESS, + VALID_TUTEE_SUBJECT, VALID_TUTEE_GRADE, VALID_TUTEE_EDUCATION_LEVEL, + VALID_TUTEE_SCHOOL, VALID_TUTEE_TAGS); + String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, Name.class.getSimpleName()); + Assert.assertThrows(IllegalValueException.class, expectedMessage, person::toModelType); + } + + @Test + public void toModelType_invalidTuteePhone_throwsIllegalValueException() { + XmlAdaptedPerson person = + new XmlAdaptedPerson(VALID_TUTEE_NAME, INVALID_PHONE, VALID_TUTEE_EMAIL, VALID_TUTEE_ADDRESS, + VALID_TUTEE_SUBJECT, VALID_TUTEE_GRADE, VALID_TUTEE_EDUCATION_LEVEL, + VALID_TUTEE_SCHOOL, VALID_TUTEE_TAGS); + String expectedMessage = Phone.MESSAGE_PHONE_CONSTRAINTS; + Assert.assertThrows(IllegalValueException.class, expectedMessage, person::toModelType); + } + + @Test + public void toModelType_nullTuteePhone_throwsIllegalValueException() { + XmlAdaptedPerson person = + new XmlAdaptedPerson(VALID_TUTEE_NAME, null, VALID_TUTEE_EMAIL, VALID_TUTEE_ADDRESS, + VALID_TUTEE_SUBJECT, VALID_TUTEE_GRADE, VALID_TUTEE_EDUCATION_LEVEL, + VALID_TUTEE_SCHOOL, VALID_TUTEE_TAGS); + String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, Phone.class.getSimpleName()); + Assert.assertThrows(IllegalValueException.class, expectedMessage, person::toModelType); + } + + @Test + public void toModelType_invalidTuteeEmail_throwsIllegalValueException() { + XmlAdaptedPerson person = + new XmlAdaptedPerson(VALID_TUTEE_NAME, VALID_TUTEE_PHONE, INVALID_EMAIL, VALID_TUTEE_ADDRESS, + VALID_TUTEE_SUBJECT, VALID_TUTEE_GRADE, VALID_TUTEE_EDUCATION_LEVEL, + VALID_TUTEE_SCHOOL, VALID_TUTEE_TAGS); + String expectedMessage = Email.MESSAGE_EMAIL_CONSTRAINTS; + Assert.assertThrows(IllegalValueException.class, expectedMessage, person::toModelType); + } + + @Test + public void toModelType_nullTuteeEmail_throwsIllegalValueException() { + XmlAdaptedPerson person = + new XmlAdaptedPerson(VALID_TUTEE_NAME, VALID_TUTEE_PHONE, null, VALID_TUTEE_ADDRESS, + VALID_TUTEE_SUBJECT, VALID_TUTEE_GRADE, VALID_TUTEE_EDUCATION_LEVEL, + VALID_TUTEE_SCHOOL, VALID_TUTEE_TAGS); + String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, Email.class.getSimpleName()); + Assert.assertThrows(IllegalValueException.class, expectedMessage, person::toModelType); + } + + @Test + public void toModelType_invalidTuteeAddress_throwsIllegalValueException() { + XmlAdaptedPerson person = + new XmlAdaptedPerson(VALID_TUTEE_NAME, VALID_TUTEE_PHONE, VALID_TUTEE_EMAIL, INVALID_ADDRESS, + VALID_TUTEE_SUBJECT, VALID_TUTEE_GRADE, VALID_TUTEE_EDUCATION_LEVEL, + VALID_TUTEE_SCHOOL, VALID_TUTEE_TAGS); + String expectedMessage = Address.MESSAGE_ADDRESS_CONSTRAINTS; + Assert.assertThrows(IllegalValueException.class, expectedMessage, person::toModelType); + } + + @Test + public void toModelType_nullTuteeAddress_throwsIllegalValueException() { + XmlAdaptedPerson person = + new XmlAdaptedPerson(VALID_TUTEE_NAME, VALID_TUTEE_PHONE, VALID_TUTEE_EMAIL, null, + VALID_TUTEE_SUBJECT, VALID_TUTEE_GRADE, VALID_TUTEE_EDUCATION_LEVEL, + VALID_TUTEE_SCHOOL, VALID_TUTEE_TAGS); + String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, Address.class.getSimpleName()); + Assert.assertThrows(IllegalValueException.class, expectedMessage, person::toModelType); + } + + @Test + public void toModelType_invalidSubject_throwsIllegalValueException() { + XmlAdaptedPerson person = + new XmlAdaptedPerson(VALID_TUTEE_NAME, VALID_TUTEE_PHONE, VALID_TUTEE_EMAIL, VALID_TUTEE_ADDRESS, + INVALID_SUBJECT, VALID_TUTEE_GRADE, VALID_TUTEE_EDUCATION_LEVEL, + VALID_TUTEE_SCHOOL, VALID_TUTEE_TAGS); + String expectedMessage = Subject.MESSAGE_SUBJECT_CONSTRAINTS; + Assert.assertThrows(IllegalValueException.class, expectedMessage, person::toModelType); + } + + @Test + public void toModelType_nullSubject_throwsIllegalValueException() { + XmlAdaptedPerson person = + new XmlAdaptedPerson(VALID_TUTEE_NAME, VALID_TUTEE_PHONE, VALID_TUTEE_EMAIL, VALID_TUTEE_ADDRESS, + null, VALID_TUTEE_GRADE, VALID_TUTEE_EDUCATION_LEVEL, + VALID_TUTEE_SCHOOL, VALID_TUTEE_TAGS); + String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, Subject.class.getSimpleName()); + Assert.assertThrows(IllegalValueException.class, expectedMessage, person::toModelType); + } + + @Test + public void toModelType_invalidGrade_throwsIllegalValueException() { + XmlAdaptedPerson person = + new XmlAdaptedPerson(VALID_TUTEE_NAME, VALID_TUTEE_PHONE, VALID_TUTEE_EMAIL, VALID_TUTEE_ADDRESS, + VALID_TUTEE_SUBJECT, INVALID_GRADE, VALID_TUTEE_EDUCATION_LEVEL, + VALID_TUTEE_SCHOOL, VALID_TUTEE_TAGS); + String expectedMessage = Grade.MESSAGE_GRADE_CONSTRAINTS; + Assert.assertThrows(IllegalValueException.class, expectedMessage, person::toModelType); + } + + @Test + public void toModelType_nullGrade_throwsIllegalValueException() { + XmlAdaptedPerson person = + new XmlAdaptedPerson(VALID_TUTEE_NAME, VALID_TUTEE_PHONE, VALID_TUTEE_EMAIL, VALID_TUTEE_ADDRESS, + VALID_TUTEE_SUBJECT, null, VALID_TUTEE_EDUCATION_LEVEL, + VALID_TUTEE_SCHOOL, VALID_TUTEE_TAGS); + String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, Grade.class.getSimpleName()); + Assert.assertThrows(IllegalValueException.class, expectedMessage, person::toModelType); + } + + @Test + public void toModelType_invalidEducationLevel_throwsIllegalValueException() { + XmlAdaptedPerson person = + new XmlAdaptedPerson(VALID_TUTEE_NAME, VALID_TUTEE_PHONE, VALID_TUTEE_EMAIL, VALID_TUTEE_ADDRESS, + VALID_TUTEE_SUBJECT, VALID_TUTEE_GRADE, INVALID_EDUCATION_LEVEL, + VALID_TUTEE_SCHOOL, VALID_TUTEE_TAGS); + String expectedMessage = EducationLevel.MESSAGE_EDUCATION_LEVEL_CONSTRAINTS; + Assert.assertThrows(IllegalValueException.class, expectedMessage, person::toModelType); + } + + @Test + public void toModelType_nullEducationLevel_throwsIllegalValueException() { + XmlAdaptedPerson person = + new XmlAdaptedPerson(VALID_TUTEE_NAME, VALID_TUTEE_PHONE, VALID_TUTEE_EMAIL, VALID_TUTEE_ADDRESS, + VALID_TUTEE_SUBJECT, VALID_TUTEE_GRADE, null, + VALID_TUTEE_SCHOOL, VALID_TUTEE_TAGS); + String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, EducationLevel.class.getSimpleName()); + Assert.assertThrows(IllegalValueException.class, expectedMessage, person::toModelType); + } + + @Test + public void toModelType_invalidSchool_throwsIllegalValueException() { + XmlAdaptedPerson person = + new XmlAdaptedPerson(VALID_TUTEE_NAME, VALID_TUTEE_PHONE, VALID_TUTEE_EMAIL, VALID_TUTEE_ADDRESS, + VALID_TUTEE_SUBJECT, VALID_TUTEE_GRADE, VALID_TUTEE_EDUCATION_LEVEL, + INVALID_SCHOOL, VALID_TUTEE_TAGS); + String expectedMessage = School.MESSAGE_SCHOOL_CONSTRAINTS; + Assert.assertThrows(IllegalValueException.class, expectedMessage, person::toModelType); + } + + @Test + public void toModelType_nullSchool_throwsIllegalValueException() { + XmlAdaptedPerson person = + new XmlAdaptedPerson(VALID_TUTEE_NAME, VALID_TUTEE_PHONE, VALID_TUTEE_EMAIL, VALID_TUTEE_ADDRESS, + VALID_TUTEE_SUBJECT, VALID_TUTEE_GRADE, VALID_TUTEE_EDUCATION_LEVEL, + null, VALID_TUTEE_TAGS); + String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, School.class.getSimpleName()); + Assert.assertThrows(IllegalValueException.class, expectedMessage, person::toModelType); + } + + @Test + public void toModelType_tuteeHasInvalidTags_throwsIllegalValueException() { + List invalidTags = new ArrayList<>(VALID_TUTEE_TAGS); + invalidTags.add(new XmlAdaptedTag(INVALID_TAG)); + XmlAdaptedPerson person = + new XmlAdaptedPerson(VALID_TUTEE_NAME, VALID_TUTEE_PHONE, VALID_TUTEE_EMAIL, VALID_TUTEE_ADDRESS, + VALID_TUTEE_SUBJECT, VALID_TUTEE_GRADE, VALID_TUTEE_EDUCATION_LEVEL, + VALID_TUTEE_SCHOOL, invalidTags); + Assert.assertThrows(IllegalValueException.class, person::toModelType); + } } diff --git a/src/test/java/seedu/address/storage/XmlAdaptedTaskTest.java b/src/test/java/seedu/address/storage/XmlAdaptedTaskTest.java new file mode 100644 index 000000000000..a6e8abb7c58d --- /dev/null +++ b/src/test/java/seedu/address/storage/XmlAdaptedTaskTest.java @@ -0,0 +1,44 @@ +package seedu.address.storage; + +import static org.junit.Assert.assertEquals; +import static seedu.address.testutil.TypicalTasks.EXAMPLE1; + +import org.junit.Test; + +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.testutil.Assert; +//@@author a-shakra +public class XmlAdaptedTaskTest { + private static final String VALID_DESCRIPTION = "A description"; + + private static final String VALID_DURATION = EXAMPLE1.getDuration(); + private static final String VALID_DATEANDTIME = EXAMPLE1.getTaskDateTime().toString(); //Double check this + + @Test + public void toModelType_validTaskDetails_returnsTask() throws Exception { + XmlAdaptedTask task = new XmlAdaptedTask(EXAMPLE1); + assertEquals(EXAMPLE1, task.toModelType()); + } + + @Test + public void toModelType_nullDescription_throwsIllegalValueException() { + XmlAdaptedTask task = new XmlAdaptedTask(null, VALID_DURATION, VALID_DATEANDTIME); + String expectedMessage = "Task's Tasks Should have a non-empty description field is missing!"; + Assert.assertThrows(IllegalValueException.class, expectedMessage, task::toModelType); + } + + @Test + public void toModelType_nullDuration_throwsIllegalValueException() { + XmlAdaptedTask task = new XmlAdaptedTask(VALID_DESCRIPTION, null, VALID_DATEANDTIME); + String expectedMessage = "Task's Duration must be a non-null value field is missing!"; + Assert.assertThrows(IllegalValueException.class, expectedMessage, task::toModelType); + } + + @Test + public void toModelType_nullDateAndTime_throwsNullPointerException() { + XmlAdaptedTask task = new XmlAdaptedTask(VALID_DESCRIPTION, VALID_DURATION, null); + String expectedMessage = "text"; + Assert.assertThrows(NullPointerException.class, expectedMessage, task::toModelType); + } + +} diff --git a/src/test/java/seedu/address/storage/XmlAddressBookStorageTest.java b/src/test/java/seedu/address/storage/XmlAddressBookStorageTest.java index 1bf3765cfba9..5063e7864c62 100644 --- a/src/test/java/seedu/address/storage/XmlAddressBookStorageTest.java +++ b/src/test/java/seedu/address/storage/XmlAddressBookStorageTest.java @@ -2,10 +2,12 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; -import static seedu.address.testutil.TypicalPersons.ALICE; -import static seedu.address.testutil.TypicalPersons.HOON; -import static seedu.address.testutil.TypicalPersons.IDA; -import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook; +import static seedu.address.testutil.TypicalTasks.EXAMPLE2; +import static seedu.address.testutil.TypicalTasks.EXAMPLE4; +import static seedu.address.testutil.typicaladdressbook.TypicalAddressBookCompiler.getTypicalAddressBook1; +import static seedu.address.testutil.typicaladdressbook.TypicalPersons.ALICE; +import static seedu.address.testutil.typicaladdressbook.TypicalPersons.HOON; +import static seedu.address.testutil.typicaladdressbook.TypicalPersons.IDA; import java.io.IOException; @@ -18,6 +20,7 @@ import seedu.address.commons.util.FileUtil; import seedu.address.model.AddressBook; import seedu.address.model.ReadOnlyAddressBook; +import seedu.address.testutil.TypicalTasks; public class XmlAddressBookStorageTest { private static final String TEST_DATA_FOLDER = FileUtil.getPath("./src/test/data/XmlAddressBookStorageTest/"); @@ -72,10 +75,11 @@ public void readAddressBook_invalidAndValidPersonAddressBook_throwDataConversion readAddressBook("invalidAndValidPersonAddressBook.xml"); } + @Test - public void readAndSaveAddressBook_allInOrder_success() throws Exception { + public void readAndSaveAddressBook_allInOrder_personSuccess() throws Exception { String filePath = testFolder.getRoot().getPath() + "TempAddressBook.xml"; - AddressBook original = getTypicalAddressBook(); + AddressBook original = getTypicalAddressBook1(); XmlAddressBookStorage xmlAddressBookStorage = new XmlAddressBookStorage(filePath); //Save in new file and read back @@ -96,6 +100,32 @@ public void readAndSaveAddressBook_allInOrder_success() throws Exception { readBack = xmlAddressBookStorage.readAddressBook().get(); //file path not specified assertEquals(original, new AddressBook(readBack)); + } + //@@author a-shakra + @Test + public void readAndSaveAddressBook_allInOrder_taskSuccess() throws Exception { + String filePath = testFolder.getRoot().getPath() + "TempAddressBook.xml"; + AddressBook original = TypicalTasks.getTypicalAddressBook(); + XmlAddressBookStorage xmlAddressBookStorage = new XmlAddressBookStorage(filePath); + + //Save in new file and read back + xmlAddressBookStorage.saveAddressBook(original, filePath); + ReadOnlyAddressBook readBack = xmlAddressBookStorage.readAddressBook(filePath).get(); + assertEquals(original, new AddressBook(readBack)); + + //Modify data, overwrite exiting file, and read back + original.removeTask(EXAMPLE2); + original.addTask(EXAMPLE2); + xmlAddressBookStorage.saveAddressBook(original, filePath); + readBack = xmlAddressBookStorage.readAddressBook(filePath).get(); + assertEquals(original, new AddressBook(readBack)); + + //Save and read without specifying file path + original.addTask(EXAMPLE4); + xmlAddressBookStorage.saveAddressBook(original); //file path not specified + readBack = xmlAddressBookStorage.readAddressBook().get(); //file path not specified + assertEquals(original, new AddressBook(readBack)); + } @Test @@ -121,5 +151,4 @@ public void saveAddressBook_nullFilePath_throwsNullPointerException() throws IOE saveAddressBook(new AddressBook(), null); } - } diff --git a/src/test/java/seedu/address/storage/XmlSerializableAddressBookTest.java b/src/test/java/seedu/address/storage/XmlSerializableAddressBookTest.java index cf405c9729c1..8d073be6ecc1 100644 --- a/src/test/java/seedu/address/storage/XmlSerializableAddressBookTest.java +++ b/src/test/java/seedu/address/storage/XmlSerializableAddressBookTest.java @@ -1,7 +1,5 @@ package seedu.address.storage; -import static org.junit.Assert.assertEquals; - import java.io.File; import org.junit.Rule; @@ -11,28 +9,17 @@ import seedu.address.commons.exceptions.IllegalValueException; import seedu.address.commons.util.FileUtil; import seedu.address.commons.util.XmlUtil; -import seedu.address.model.AddressBook; -import seedu.address.testutil.TypicalPersons; public class XmlSerializableAddressBookTest { private static final String TEST_DATA_FOLDER = FileUtil.getPath("src/test/data/XmlSerializableAddressBookTest/"); - private static final File TYPICAL_PERSONS_FILE = new File(TEST_DATA_FOLDER + "typicalPersonsAddressBook.xml"); private static final File INVALID_PERSON_FILE = new File(TEST_DATA_FOLDER + "invalidPersonAddressBook.xml"); + private static final File INVALID_TASK_FILE = new File(TEST_DATA_FOLDER + "invalidTaskAddressBook.xml"); private static final File INVALID_TAG_FILE = new File(TEST_DATA_FOLDER + "invalidTagAddressBook.xml"); @Rule public ExpectedException thrown = ExpectedException.none(); - @Test - public void toModelType_typicalPersonsFile_success() throws Exception { - XmlSerializableAddressBook dataFromFile = XmlUtil.getDataFromFile(TYPICAL_PERSONS_FILE, - XmlSerializableAddressBook.class); - AddressBook addressBookFromFile = dataFromFile.toModelType(); - AddressBook typicalPersonsAddressBook = TypicalPersons.getTypicalAddressBook(); - assertEquals(addressBookFromFile, typicalPersonsAddressBook); - } - @Test public void toModelType_invalidPersonFile_throwsIllegalValueException() throws Exception { XmlSerializableAddressBook dataFromFile = XmlUtil.getDataFromFile(INVALID_PERSON_FILE, @@ -40,7 +27,14 @@ public void toModelType_invalidPersonFile_throwsIllegalValueException() throws E thrown.expect(IllegalValueException.class); dataFromFile.toModelType(); } - + //@@author a-shakra + @Test + public void toModelType_invalidTaskFile_throwsNullValueException() throws Exception { + XmlSerializableAddressBook dataFromFile = XmlUtil.getDataFromFile(INVALID_TASK_FILE, + XmlSerializableAddressBook.class); + thrown.expect(NullPointerException.class); + dataFromFile.toModelType(); + } @Test public void toModelType_invalidTagFile_throwsIllegalValueException() throws Exception { XmlSerializableAddressBook dataFromFile = XmlUtil.getDataFromFile(INVALID_TAG_FILE, diff --git a/src/test/java/seedu/address/testutil/AddressBookBuilder.java b/src/test/java/seedu/address/testutil/AddressBookBuilder.java index 6e73a762b0c1..b1b4e67af7f1 100644 --- a/src/test/java/seedu/address/testutil/AddressBookBuilder.java +++ b/src/test/java/seedu/address/testutil/AddressBookBuilder.java @@ -2,9 +2,11 @@ import seedu.address.commons.exceptions.IllegalValueException; import seedu.address.model.AddressBook; +import seedu.address.model.Task; import seedu.address.model.person.Person; import seedu.address.model.person.exceptions.DuplicatePersonException; import seedu.address.model.tag.Tag; +import seedu.address.model.task.exceptions.TimingClashException; /** * A utility class to help with building Addressbook objects. @@ -47,6 +49,18 @@ public AddressBookBuilder withTag(String tagName) { return this; } + /** + * Adds a new {@code Task} to the {@code AddressBook} that we are building. + */ + public AddressBookBuilder withTask(Task task) { + try { + addressBook.addTask(task); + } catch (TimingClashException tce) { + throw new IllegalArgumentException("Timing clash has occurred"); + } + return this; + } + public AddressBook build() { return addressBook; } diff --git a/src/test/java/seedu/address/testutil/EditPersonDescriptorBuilder.java b/src/test/java/seedu/address/testutil/EditPersonDescriptorBuilder.java index 4584bd5044e1..27adec75f3f3 100644 --- a/src/test/java/seedu/address/testutil/EditPersonDescriptorBuilder.java +++ b/src/test/java/seedu/address/testutil/EditPersonDescriptorBuilder.java @@ -11,6 +11,11 @@ import seedu.address.model.person.Person; import seedu.address.model.person.Phone; import seedu.address.model.tag.Tag; +import seedu.address.model.tutee.EducationLevel; +import seedu.address.model.tutee.Grade; +import seedu.address.model.tutee.School; +import seedu.address.model.tutee.Subject; +import seedu.address.model.tutee.Tutee; /** * A utility class to help with building EditPersonDescriptor objects. @@ -39,6 +44,24 @@ public EditPersonDescriptorBuilder(Person person) { descriptor.setTags(person.getTags()); } + //@@author ChoChihTun + /** + * Returns an {@code EditPersonDescriptor} with fields containing {@code tutee}'s details + */ + public EditPersonDescriptorBuilder(Tutee tutee) { + descriptor = new EditPersonDescriptor(); + descriptor.setName(tutee.getName()); + descriptor.setPhone(tutee.getPhone()); + descriptor.setEmail(tutee.getEmail()); + descriptor.setAddress(tutee.getAddress()); + descriptor.setSubject(tutee.getSubject()); + descriptor.setGrade(tutee.getGrade()); + descriptor.setEducationLevel(tutee.getEducationLevel()); + descriptor.setSchool(tutee.getSchool()); + descriptor.setTags(tutee.getTags()); + } + //@@author + /** * Sets the {@code Name} of the {@code EditPersonDescriptor} that we are building. */ @@ -71,6 +94,42 @@ public EditPersonDescriptorBuilder withAddress(String address) { return this; } + //@@author ChoChihTun + /** + * Sets the {@code Subject} of the {@code EditPersonDescriptor} that we are building. + */ + public EditPersonDescriptorBuilder withSubject(String subject) { + descriptor.setSubject(new Subject(subject)); + return this; + } + + /** + * Sets the {@code Grade} of the {@code EditPersonDescriptor} that we are building. + */ + public EditPersonDescriptorBuilder withGrade(String grade) { + descriptor.setGrade(new Grade(grade)); + return this; + } + + + /** + * Sets the {@code EducationLevel} of the {@code EditPersonDescriptor} that we are building. + */ + public EditPersonDescriptorBuilder withEducationLevel(String educationLevel) { + descriptor.setEducationLevel(new EducationLevel(educationLevel)); + return this; + } + + + /** + * Sets the {@code School} of the {@code EditPersonDescriptor} that we are building. + */ + public EditPersonDescriptorBuilder withSchool(String school) { + descriptor.setSchool(new School(school)); + return this; + } + //@@author + /** * Parses the {@code tags} into a {@code Set} and set it to the {@code EditPersonDescriptor} * that we are building. diff --git a/src/test/java/seedu/address/testutil/ModelStub.java b/src/test/java/seedu/address/testutil/ModelStub.java new file mode 100644 index 000000000000..f9de71c9fdfd --- /dev/null +++ b/src/test/java/seedu/address/testutil/ModelStub.java @@ -0,0 +1,95 @@ +package seedu.address.testutil; + +import static org.junit.Assert.fail; + +import java.util.Comparator; +import java.util.function.Predicate; + +import javafx.collections.ObservableList; + +import seedu.address.model.Model; +import seedu.address.model.ReadOnlyAddressBook; +import seedu.address.model.Task; +import seedu.address.model.person.Person; +import seedu.address.model.person.exceptions.DuplicatePersonException; +import seedu.address.model.person.exceptions.PersonNotFoundException; +import seedu.address.model.task.exceptions.TimingClashException; + +/** + * A default model stub that have all of the methods failing. + */ +public class ModelStub implements Model { + @Override + public void addPerson(Person person) throws DuplicatePersonException { + fail("This method should not be called."); + } + + @Override + public void addTask(Task task) throws TimingClashException { + fail("This method should not be called"); + } + + @Override + public void deleteTask(Task task) { + fail("This method should not be called"); + } + + @Override + public ObservableList getFilteredTaskList() { + fail("This method should not be called."); + return null; + } + + @Override + public void updateFilteredTaskList(Predicate predicate) { + fail("This method should not be called."); + } + + @Override + public void resetData(ReadOnlyAddressBook newData) { + fail("This method should not be called."); + } + + @Override + public ReadOnlyAddressBook getAddressBook() { + fail("This method should not be called."); + return null; + } + + @Override + public void deletePerson(Person target) throws PersonNotFoundException { + fail("This method should not be called."); + } + + + + @Override + public void updatePerson(Person target, Person editedPerson) + throws DuplicatePersonException { + fail("This method should not be called."); + } + + + + @Override + public ObservableList getFilteredPersonList() { + fail("This method should not be called."); + return null; + } + + + @Override + public void updateFilteredPersonList(Predicate predicate) { + fail("This method should not be called."); + } + + @Override + public void sortFilteredPersonList(Comparator comparator) { + fail("This method should not be called."); + } + + @Override + public void sortFilteredTaskList(Comparator comparator) { + fail("This method should not be called."); + } +} diff --git a/src/test/java/seedu/address/testutil/PersonBuilder.java b/src/test/java/seedu/address/testutil/PersonBuilder.java index b124fc1d73b1..d0c0fbbee36a 100644 --- a/src/test/java/seedu/address/testutil/PersonBuilder.java +++ b/src/test/java/seedu/address/testutil/PersonBuilder.java @@ -22,11 +22,11 @@ public class PersonBuilder { public static final String DEFAULT_ADDRESS = "123, Jurong West Ave 6, #08-111"; public static final String DEFAULT_TAGS = "friends"; - private Name name; - private Phone phone; - private Email email; - private Address address; - private Set tags; + protected Name name; + protected Phone phone; + protected Email email; + protected Address address; + protected Set tags; public PersonBuilder() { name = new Name(DEFAULT_NAME); diff --git a/src/test/java/seedu/address/testutil/TaskBuilder.java b/src/test/java/seedu/address/testutil/TaskBuilder.java new file mode 100644 index 000000000000..b5ca8c6be723 --- /dev/null +++ b/src/test/java/seedu/address/testutil/TaskBuilder.java @@ -0,0 +1,103 @@ +package seedu.address.testutil; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.time.format.ResolverStyle; + +import seedu.address.model.personal.PersonalTask; +import seedu.address.model.tutee.TuitionTask; + +/** + * A utility class to help with building PersonalTask and TuitionTask objects + */ +//@@author a-shakra +public class TaskBuilder { + + public static final String DEFAULT_TUTEE_NAME = "Alice Pauline"; + public static final String DEFAULT_DATE = "12/12/2016"; + public static final String DEFAULT_TIME = "12:00"; + public static final String DEFAULT_DATE_TIME = DEFAULT_DATE + " " + DEFAULT_TIME; + public static final String DEFAULT_DURATION = "1h30m"; + public static final String DEFAULT_DESCRIPTION = "Alice's homework"; + + private static final String EMPTY_STRING = ""; + + + protected String name; + protected String description; + protected String duration; + protected LocalDateTime dateAndTime; + + private DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd/MM/uuuu HH:mm") + .withResolverStyle(ResolverStyle.STRICT); + + public TaskBuilder() { + name = DEFAULT_TUTEE_NAME; + description = DEFAULT_DESCRIPTION; + duration = DEFAULT_DURATION; + dateAndTime = LocalDateTime.parse(DEFAULT_DATE_TIME, formatter); + } + + /** + * Initializes the TaskBuilder with the data of a given {@code Tuition Task}. + */ + public TaskBuilder(TuitionTask taskToCopy) { + name = taskToCopy.getPerson(); + description = taskToCopy.getDescription(); + duration = taskToCopy.getDuration(); + dateAndTime = taskToCopy.getTaskDateTime(); + } + + /** + * Sets the {@code name} of the {@code Task} that we are building. + */ + public TaskBuilder withTuteeName(String name) { + this.name = name; + return this; + } + + /** + * Sets the {@code description} of the {@code Task} that we are building. + */ + public TaskBuilder withDescription(String description) { + this.description = description; + return this; + } + + /** + * Sets the {@code description} of the {@code Task} that we are building to be empty. + */ + public TaskBuilder withoutDescription() { + this.description = EMPTY_STRING; + return this; + } + + /** + * Sets the {@code duration} of the {@code Task} that we are building. + */ + public TaskBuilder withDuration(String duration) { + this.duration = duration; + return this; + } + + /** + * Sets the {@code DateAndTime} of the {@code Task} that we are building. + */ + + public TaskBuilder withDateTime(String dateAndTime) { + + this.dateAndTime = LocalDateTime.parse(dateAndTime, formatter); + return this; + } + /** + * Ideally, this return variable should be made to a Task class or this function should return + * a tuition task as well + */ + + public PersonalTask buildPersonalTask() { + return new PersonalTask(dateAndTime, duration, description); + } + public TuitionTask buildTuitionTask() { + return new TuitionTask(name, dateAndTime, duration, description); + } +} diff --git a/src/test/java/seedu/address/testutil/TaskUtil.java b/src/test/java/seedu/address/testutil/TaskUtil.java new file mode 100644 index 000000000000..792f9e21211c --- /dev/null +++ b/src/test/java/seedu/address/testutil/TaskUtil.java @@ -0,0 +1,36 @@ +package seedu.address.testutil; + +import java.time.format.DateTimeFormatter; +import java.time.format.ResolverStyle; + +import seedu.address.logic.commands.AddPersonalTaskCommand; +import seedu.address.model.Task; + + +/** + * A utility class for Task. + */ +//@@author a-shakra +public class TaskUtil { + public static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("dd/MM/uuuu HH:mm") + .withResolverStyle(ResolverStyle.STRICT); + + /** + * Returns an add personal task command string for adding the {@code task}. + */ + public static String getAddPersonalTaskCommand(Task task) { + return AddPersonalTaskCommand.COMMAND_WORD + " " + getPersonalTaskDetails(task); + } + + /** + * Returns the part of command string for the given {@code task}'s details. + */ + public static String getPersonalTaskDetails(Task task) { + StringBuilder sb = new StringBuilder(); + sb.append(task.getStringTaskDateTime() + " "); + sb.append(task.getDuration() + " "); + sb.append(task.getDescription() + " "); + return sb.toString(); + } + +} diff --git a/src/test/java/seedu/address/testutil/TuteeBuilder.java b/src/test/java/seedu/address/testutil/TuteeBuilder.java new file mode 100644 index 000000000000..a7570c9cda54 --- /dev/null +++ b/src/test/java/seedu/address/testutil/TuteeBuilder.java @@ -0,0 +1,136 @@ +package seedu.address.testutil; + +import java.util.HashSet; + +import seedu.address.model.person.Address; +import seedu.address.model.person.Email; +import seedu.address.model.person.Name; +import seedu.address.model.person.Phone; +import seedu.address.model.tag.Tag; +import seedu.address.model.tutee.EducationLevel; +import seedu.address.model.tutee.Grade; +import seedu.address.model.tutee.School; +import seedu.address.model.tutee.Subject; +import seedu.address.model.tutee.Tutee; +import seedu.address.model.util.SampleDataUtil; + +//@@author ChoChihTun +/** + * A utility class to help with building Tutee objects. + */ +public class TuteeBuilder extends PersonBuilder { + public static final String DEFAULT_SUBJECT = "mathematics"; + public static final String DEFAULT_GRADE = "C+"; + public static final String DEFAULT_EDUCATION_LEVEL = "secondary"; + public static final String DEFAULT_SCHOOL = "fengshan secondary school"; + public static final String TUTEE_TAG = "Tutee"; + + private Subject subject; + private Grade grade; + private EducationLevel educationLevel; + private School school; + + public TuteeBuilder() { + name = new Name(DEFAULT_NAME); + phone = new Phone(DEFAULT_PHONE); + email = new Email(DEFAULT_EMAIL); + address = new Address(DEFAULT_ADDRESS); + subject = new Subject(DEFAULT_SUBJECT); + grade = new Grade(DEFAULT_GRADE); + educationLevel = new EducationLevel(DEFAULT_EDUCATION_LEVEL); + school = new School(DEFAULT_SCHOOL); + tags = SampleDataUtil.getTagSet(DEFAULT_TAGS); + tags.add(new Tag(TUTEE_TAG)); + } + + /** + * Initializes the PersonBuilder with the data of {@code personToCopy}. + */ + public TuteeBuilder(Tutee tuteeToCopy) { + name = tuteeToCopy.getName(); + phone = tuteeToCopy.getPhone(); + email = tuteeToCopy.getEmail(); + address = tuteeToCopy.getAddress(); + subject = tuteeToCopy.getSubject(); + grade = tuteeToCopy.getGrade(); + educationLevel = tuteeToCopy.getEducationLevel(); + school = tuteeToCopy.getSchool(); + tags = new HashSet<>(tuteeToCopy.getTags()); + } + + /** + * Sets the {@code Name} of the {@code Tutee} that we are building. + */ + public TuteeBuilder withName(String name) { + this.name = new Name(name); + return this; + } + + /** + * Parses the {@code tags} into a {@code Set} and set it to the {@code Tutee} that we are building. + */ + public TuteeBuilder withTags(String ... tags) { + this.tags = SampleDataUtil.getTagSet(tags); + this.tags.add(new Tag("Tutee")); + return this; + } + + /** + * Sets the {@code Address} of the {@code Tutee} that we are building. + */ + public TuteeBuilder withAddress(String address) { + this.address = new Address(address); + return this; + } + + /** + * Sets the {@code Phone} of the {@code Tutee} that we are building. + */ + public TuteeBuilder withPhone(String phone) { + this.phone = new Phone(phone); + return this; + } + + /** + * Sets the {@code Email} of the {@code Tutee} that we are building. + */ + public TuteeBuilder withEmail(String email) { + this.email = new Email(email); + return this; + } + + /** + * Sets the {@code Subject} of the {@code Tutee} that we are building. + */ + public TuteeBuilder withSubject(String subject) { + this.subject = new Subject(subject); + return this; + } + /** + * Sets the {@code Grade} of the {@code Tutee} that we are building. + */ + public TuteeBuilder withGrade(String grade) { + this.grade = new Grade(grade); + return this; + } + /** + * Sets the {@code EducationLevel} of the {@code Tutee} that we are building. + */ + public TuteeBuilder withEducationLevel(String educationLevel) { + this.educationLevel = new EducationLevel(educationLevel); + return this; + } + /** + * Sets the {@code School} of the {@code Tutee} that we are building. + */ + public TuteeBuilder withSchool(String school) { + this.school = new School(school); + return this; + } + + + public Tutee build() { + return new Tutee(name, phone, email, address, subject, grade, educationLevel, school, tags); + } + +} diff --git a/src/test/java/seedu/address/testutil/TuteeUtil.java b/src/test/java/seedu/address/testutil/TuteeUtil.java new file mode 100644 index 000000000000..2aa26111d175 --- /dev/null +++ b/src/test/java/seedu/address/testutil/TuteeUtil.java @@ -0,0 +1,47 @@ +package seedu.address.testutil; + +import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_EDUCATION_LEVEL; +import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; +import static seedu.address.logic.parser.CliSyntax.PREFIX_GRADE; +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_SCHOOL; +import static seedu.address.logic.parser.CliSyntax.PREFIX_SUBJECT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; + +import seedu.address.logic.commands.AddTuteeCommand; +import seedu.address.model.tutee.Tutee; + +//@@author ChoChihTun +/** + * A utility class for Tutee. + */ +public class TuteeUtil { + + /** + * Returns an addtutee command string for adding the {@code tutee}. + */ + public static String getAddTuteeCommand(Tutee tutee) { + return AddTuteeCommand.COMMAND_WORD + " " + getTuteeDetails(tutee); + } + + /** + * Returns the part of command string for the given {@code tutee}'s details. + */ + public static String getTuteeDetails(Tutee tutee) { + StringBuilder sb = new StringBuilder(); + sb.append(PREFIX_NAME + tutee.getName().fullName + " "); + sb.append(PREFIX_PHONE + tutee.getPhone().value + " "); + sb.append(PREFIX_EMAIL + tutee.getEmail().value + " "); + sb.append(PREFIX_ADDRESS + tutee.getAddress().value + " "); + sb.append(PREFIX_SUBJECT + tutee.getSubject().subject + " "); + sb.append(PREFIX_GRADE + tutee.getGrade().grade + " "); + sb.append(PREFIX_EDUCATION_LEVEL + tutee.getEducationLevel().educationLevel + " "); + sb.append(PREFIX_SCHOOL + tutee.getSchool().school + " "); + tutee.getTags().stream().forEach( + s -> sb.append(PREFIX_TAG + s.tagName + " ") + ); + return sb.toString(); + } +} diff --git a/src/test/java/seedu/address/testutil/TypicalCalendarEntries.java b/src/test/java/seedu/address/testutil/TypicalCalendarEntries.java new file mode 100644 index 000000000000..4e8ce464eb6f --- /dev/null +++ b/src/test/java/seedu/address/testutil/TypicalCalendarEntries.java @@ -0,0 +1,68 @@ +package seedu.address.testutil; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.time.format.ResolverStyle; + +import com.calendarfx.model.Entry; +import com.calendarfx.model.Interval; + +//@@author ChoChihTun +/** + * A utility class containing a list of {@code Entry} objects to be used in tests. + */ +public class TypicalCalendarEntries { + public static final String VALID_NAME = "Jason"; + public static final String VALID_DURATION = "1h30m"; + public static final String VALID_DESCRIPTION = "homework 1"; + private static final String VALID_STRING_START_DATE_TIME = "01/04/2018 11:00"; + private static final String VALID_STRING_END_DATE_TIME = "01/04/2018 12:30"; + + private static DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd/MM/uuuu HH:mm") + .withResolverStyle(ResolverStyle.STRICT); + public static final LocalDateTime VALID_START_DATE_TIME = + LocalDateTime.parse(VALID_STRING_START_DATE_TIME, formatter); + public static final LocalDateTime VALID_END_DATE_TIME = + LocalDateTime.parse(VALID_STRING_END_DATE_TIME, formatter); + + private static Entry validTuitionEntry; + private static Entry validPersonalEntry; + + /** + * Creates valid calendar entry + * + */ + public TypicalCalendarEntries() { + Interval interval = new Interval(VALID_START_DATE_TIME, VALID_END_DATE_TIME); + createTuitionEntry(interval); + createPersonalEntry(interval); + } + + /** + * Creates a valid tuition calendar entry + * + * @param interval of the entry + */ + private void createTuitionEntry(Interval interval) { + validTuitionEntry = new Entry(VALID_NAME); + validTuitionEntry.setInterval(interval); + } + + /** + * Creates a valid personal calendar entry + * + * @param interval of the entry + */ + private void createPersonalEntry(Interval interval) { + validPersonalEntry = new Entry(VALID_DESCRIPTION); + validPersonalEntry.setInterval(interval); + } + + public static Entry getTuitionEntry() { + return validTuitionEntry; + } + + public static Entry getPersonalEntry() { + return validPersonalEntry; + } +} diff --git a/src/test/java/seedu/address/testutil/TypicalIndexes.java b/src/test/java/seedu/address/testutil/TypicalIndexes.java index 1e6139376570..b1acc1bfa98d 100644 --- a/src/test/java/seedu/address/testutil/TypicalIndexes.java +++ b/src/test/java/seedu/address/testutil/TypicalIndexes.java @@ -9,4 +9,7 @@ public class TypicalIndexes { public static final Index INDEX_FIRST_PERSON = Index.fromOneBased(1); public static final Index INDEX_SECOND_PERSON = Index.fromOneBased(2); public static final Index INDEX_THIRD_PERSON = Index.fromOneBased(3); + public static final Index INDEX_FIRST_TASK = Index.fromOneBased(1); + public static final Index INDEX_SECOND_TASK = Index.fromOneBased(2); + public static final Index INDEX_THIRD_TASK = Index.fromOneBased(3); } 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 000000000000..aadac9a9ca33 --- /dev/null +++ b/src/test/java/seedu/address/testutil/TypicalTasks.java @@ -0,0 +1,46 @@ +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; +import seedu.address.model.task.exceptions.TimingClashException; + +/** + * A utility class for Task. For now, this is customized to the personal task class + */ +//@@author a-shakra +public class TypicalTasks { + + public static final Task EXAMPLE1 = new TaskBuilder().withTuteeName(null).withDescription("exampleTask1") + .withDuration("3h20m").withDateTime("02/05/2018 03:20").buildPersonalTask(); + public static final Task EXAMPLE2 = new TaskBuilder().withTuteeName(null).withDescription("exampleTask2") + .withDuration("3h20m").withDateTime("02/04/2018 13:20").buildPersonalTask(); + public static final Task EXAMPLE3 = new TaskBuilder().withTuteeName(null).withDescription("exampleTask3") + .withDuration("3h20m").withDateTime("02/06/2018 23:20").buildPersonalTask(); + public static final Task EXAMPLE4 = new TaskBuilder().withTuteeName(null).withDescription("exampleTask4") + .withDuration("3h20m").withDateTime("02/07/2018 23:20").buildPersonalTask(); + + private TypicalTasks() {} // prevents instantiation + /** + * Returns an {@code AddressBook} with all the typical tasks. + */ + public static AddressBook getTypicalAddressBook() { + AddressBook ab = new AddressBook(); + for (Task task : getTypicalTasks()) { + try { + ab.addTask(task); + } catch (TimingClashException e) { + throw new AssertionError("Timing Clash"); + } + } + return ab; + } + + public static List getTypicalTasks() { + return new ArrayList<>(Arrays.asList(EXAMPLE1, EXAMPLE2, EXAMPLE3)); + } + +} diff --git a/src/test/java/seedu/address/testutil/typicaladdressbook/TypicalAddressBookCompiler.java b/src/test/java/seedu/address/testutil/typicaladdressbook/TypicalAddressBookCompiler.java new file mode 100644 index 000000000000..cbf6cc1381d8 --- /dev/null +++ b/src/test/java/seedu/address/testutil/typicaladdressbook/TypicalAddressBookCompiler.java @@ -0,0 +1,61 @@ +package seedu.address.testutil.typicaladdressbook; + +import static seedu.address.testutil.typicaladdressbook.TypicalPersons.getTypicalPersons; +import static seedu.address.testutil.typicaladdressbook.TypicalTasks.getTypicalTasks; +import static seedu.address.testutil.typicaladdressbook.TypicalTutees.getTypicalPersonsAndTutees; + +import seedu.address.model.AddressBook; +import seedu.address.model.Task; +import seedu.address.model.person.Person; +import seedu.address.model.person.exceptions.DuplicatePersonException; +import seedu.address.model.task.exceptions.TimingClashException; + +/** + * A utility class providing an Address Book with its initialized data in order to support tests. + */ +public class TypicalAddressBookCompiler { + /** + * Returns an {@code AddressBook} with all the typical persons and typical tasks. + * + */ + public static AddressBook getTypicalAddressBook1() { + AddressBook ab = new AddressBook(); + for (Person person : getTypicalPersons()) { + try { + ab.addPerson(person); + } catch (DuplicatePersonException e) { + throw new AssertionError("not possible"); + } + } + for (Task task : getTypicalTasks()) { + try { + ab.addTask(task); + } catch (TimingClashException tce) { + throw new AssertionError("time clash is not possible"); + } + } + return ab; + } + + /** + * Returns an {@code AddressBook} with all the typical persons & tutees and typical tasks. + */ + public static AddressBook getTypicalAddressBook2() { + AddressBook ab = new AddressBook(); + for (Person person : getTypicalPersonsAndTutees()) { + try { + ab.addPerson(person); + } catch (DuplicatePersonException e) { + throw new AssertionError("not possible"); + } + } + for (Task task : getTypicalTasks()) { + try { + ab.addTask(task); + } catch (TimingClashException tce) { + throw new AssertionError("time clash is not possible"); + } + } + return ab; + } +} diff --git a/src/test/java/seedu/address/testutil/TypicalPersons.java b/src/test/java/seedu/address/testutil/typicaladdressbook/TypicalPersons.java similarity index 79% rename from src/test/java/seedu/address/testutil/TypicalPersons.java rename to src/test/java/seedu/address/testutil/typicaladdressbook/TypicalPersons.java index 6d7bdbfc55ed..643c7eb27730 100644 --- a/src/test/java/seedu/address/testutil/TypicalPersons.java +++ b/src/test/java/seedu/address/testutil/typicaladdressbook/TypicalPersons.java @@ -1,13 +1,17 @@ -package seedu.address.testutil; +package seedu.address.testutil.typicaladdressbook; import static seedu.address.logic.commands.CommandTestUtil.VALID_ADDRESS_AMY; import static seedu.address.logic.commands.CommandTestUtil.VALID_ADDRESS_BOB; +import static seedu.address.logic.commands.CommandTestUtil.VALID_ADDRESS_ROBERT; import static seedu.address.logic.commands.CommandTestUtil.VALID_EMAIL_AMY; import static seedu.address.logic.commands.CommandTestUtil.VALID_EMAIL_BOB; +import static seedu.address.logic.commands.CommandTestUtil.VALID_EMAIL_ROBERT; import static seedu.address.logic.commands.CommandTestUtil.VALID_NAME_AMY; import static seedu.address.logic.commands.CommandTestUtil.VALID_NAME_BOB; +import static seedu.address.logic.commands.CommandTestUtil.VALID_NAME_ROBERT; import static seedu.address.logic.commands.CommandTestUtil.VALID_PHONE_AMY; import static seedu.address.logic.commands.CommandTestUtil.VALID_PHONE_BOB; +import static seedu.address.logic.commands.CommandTestUtil.VALID_PHONE_ROBERT; import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_FRIEND; import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_HUSBAND; @@ -15,9 +19,8 @@ import java.util.Arrays; import java.util.List; -import seedu.address.model.AddressBook; import seedu.address.model.person.Person; -import seedu.address.model.person.exceptions.DuplicatePersonException; +import seedu.address.testutil.PersonBuilder; /** * A utility class containing a list of {@code Person} objects to be used in tests. @@ -29,13 +32,13 @@ public class TypicalPersons { .withPhone("85355255") .withTags("friends").build(); public static final Person BENSON = new PersonBuilder().withName("Benson Meier") - .withAddress("311, Clementi Ave 2, #02-25") - .withEmail("johnd@example.com").withPhone("98765432") - .withTags("owesMoney", "friends").build(); + .withPhone("98765432").withAddress("311, Clementi Ave 2, #02-25") + .withEmail("johnd@example.com").withTags("owesMoney", "friends").build(); public static final Person CARL = new PersonBuilder().withName("Carl Kurz").withPhone("95352563") .withEmail("heinz@example.com").withAddress("wall street").build(); public static final Person DANIEL = new PersonBuilder().withName("Daniel Meier").withPhone("87652533") - .withEmail("cornelia@example.com").withAddress("10th street").build(); + .withEmail("cornelia@example.com") + .withAddress("10th street").build(); public static final Person ELLE = new PersonBuilder().withName("Elle Meyer").withPhone("9482224") .withEmail("werner@example.com").withAddress("michegan ave").build(); public static final Person FIONA = new PersonBuilder().withName("Fiona Kunz").withPhone("9482427") @@ -55,26 +58,13 @@ public class TypicalPersons { public static final Person BOB = new PersonBuilder().withName(VALID_NAME_BOB).withPhone(VALID_PHONE_BOB) .withEmail(VALID_EMAIL_BOB).withAddress(VALID_ADDRESS_BOB).withTags(VALID_TAG_HUSBAND, VALID_TAG_FRIEND) .build(); + public static final Person ROBERT = new PersonBuilder().withName(VALID_NAME_ROBERT).withPhone(VALID_PHONE_ROBERT) + .withEmail(VALID_EMAIL_ROBERT).withAddress(VALID_ADDRESS_ROBERT).build(); public static final String KEYWORD_MATCHING_MEIER = "Meier"; // A keyword that matches MEIER private TypicalPersons() {} // prevents instantiation - /** - * Returns an {@code AddressBook} with all the typical persons. - */ - public static AddressBook getTypicalAddressBook() { - AddressBook ab = new AddressBook(); - for (Person person : getTypicalPersons()) { - try { - ab.addPerson(person); - } catch (DuplicatePersonException e) { - throw new AssertionError("not possible"); - } - } - return ab; - } - public static List getTypicalPersons() { return new ArrayList<>(Arrays.asList(ALICE, BENSON, CARL, DANIEL, ELLE, FIONA, GEORGE)); } diff --git a/src/test/java/seedu/address/testutil/typicaladdressbook/TypicalTasks.java b/src/test/java/seedu/address/testutil/typicaladdressbook/TypicalTasks.java new file mode 100644 index 000000000000..b94761b3fc51 --- /dev/null +++ b/src/test/java/seedu/address/testutil/typicaladdressbook/TypicalTasks.java @@ -0,0 +1,55 @@ +package seedu.address.testutil.typicaladdressbook; + +import static seedu.address.logic.commands.CommandTestUtil.VALID_DATE_TIME_AMY; +import static seedu.address.logic.commands.CommandTestUtil.VALID_DATE_TIME_BOB; +import static seedu.address.logic.commands.CommandTestUtil.VALID_DURATION_AMY; +import static seedu.address.logic.commands.CommandTestUtil.VALID_DURATION_BOB; +import static seedu.address.logic.commands.CommandTestUtil.VALID_NAME_AMY; +import static seedu.address.logic.commands.CommandTestUtil.VALID_NAME_BOB; +import static seedu.address.logic.commands.CommandTestUtil.VALID_TASK_DESC_AMY; +import static seedu.address.logic.commands.CommandTestUtil.VALID_TASK_DESC_BOB; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import seedu.address.model.Task; +import seedu.address.model.personal.PersonalTask; +import seedu.address.model.tutee.TuitionTask; +import seedu.address.testutil.TaskBuilder; + + +/** + * A utility class containing a list of {@code Task} objects to be used in tests. + */ +public class TypicalTasks { + public static final TuitionTask TASK_ALICE = new TaskBuilder().withTuteeName("Alice Pauline") + .withDateTime("01/10/2018 10:00").withDuration("2h0m").withDescription("Calculus page 24") + .buildTuitionTask(); + public static final TuitionTask TASK_BENSON = new TaskBuilder().withTuteeName("Benson Meier") + .withDateTime("01/10/2018 14:30").withDuration("2h0m").withDescription("Math exam") + .buildTuitionTask(); + public static final TuitionTask TASK_CARL = new TaskBuilder().withTuteeName("Carl Kurtz") + .withDateTime("31/12/2018 09:15").withDuration("1h20m").withoutDescription() + .buildTuitionTask(); + public static final PersonalTask TASK_GROCERRY_SHOPPING = new TaskBuilder() + .withDateTime("25/04/2017 14:30").withDuration("1h0m").withDescription("grocery shopping") + .buildPersonalTask(); + public static final PersonalTask TASK_YOGA = new TaskBuilder() + .withDateTime("28/02/2019 14:30").withDuration("3h0m").withDescription("yoga") + .buildPersonalTask(); + + // Manually added - Task details found in {@code CommandTestUtil} + public static final TuitionTask TASK_AMY = new TaskBuilder().withTuteeName(VALID_NAME_AMY) + .withDateTime(VALID_DATE_TIME_AMY).withDuration(VALID_DURATION_AMY).withDescription(VALID_TASK_DESC_AMY) + .buildTuitionTask(); + public static final TuitionTask TASK_BOB = new TaskBuilder().withTuteeName(VALID_NAME_BOB) + .withDateTime(VALID_DATE_TIME_BOB).withDuration(VALID_DURATION_BOB).withDescription(VALID_TASK_DESC_BOB) + .buildTuitionTask(); + + private TypicalTasks() {} // prevents instantiation + + public static List getTypicalTasks() { + return new ArrayList<>(Arrays.asList(TASK_ALICE, TASK_BENSON, TASK_CARL, TASK_GROCERRY_SHOPPING, TASK_YOGA)); + } +} diff --git a/src/test/java/seedu/address/testutil/typicaladdressbook/TypicalTutees.java b/src/test/java/seedu/address/testutil/typicaladdressbook/TypicalTutees.java new file mode 100644 index 000000000000..9a4f1fe091ee --- /dev/null +++ b/src/test/java/seedu/address/testutil/typicaladdressbook/TypicalTutees.java @@ -0,0 +1,73 @@ +package seedu.address.testutil.typicaladdressbook; + +import static seedu.address.logic.commands.CommandTestUtil.VALID_ADDRESS_AMY; +import static seedu.address.logic.commands.CommandTestUtil.VALID_ADDRESS_BOB; +import static seedu.address.logic.commands.CommandTestUtil.VALID_EDUCATION_LEVEL_AMY; +import static seedu.address.logic.commands.CommandTestUtil.VALID_EDUCATION_LEVEL_BOB; +import static seedu.address.logic.commands.CommandTestUtil.VALID_EMAIL_AMY; +import static seedu.address.logic.commands.CommandTestUtil.VALID_EMAIL_BOB; +import static seedu.address.logic.commands.CommandTestUtil.VALID_GRADE_AMY; +import static seedu.address.logic.commands.CommandTestUtil.VALID_GRADE_BOB; +import static seedu.address.logic.commands.CommandTestUtil.VALID_NAME_AMY; +import static seedu.address.logic.commands.CommandTestUtil.VALID_NAME_BOB; +import static seedu.address.logic.commands.CommandTestUtil.VALID_PHONE_AMY; +import static seedu.address.logic.commands.CommandTestUtil.VALID_PHONE_BOB; +import static seedu.address.logic.commands.CommandTestUtil.VALID_SCHOOL_AMY; +import static seedu.address.logic.commands.CommandTestUtil.VALID_SCHOOL_BOB; +import static seedu.address.logic.commands.CommandTestUtil.VALID_SUBJECT_AMY; +import static seedu.address.logic.commands.CommandTestUtil.VALID_SUBJECT_BOB; +import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_FRIEND; +import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_HUSBAND; + +import static seedu.address.testutil.typicaladdressbook.TypicalPersons.DANIEL; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import seedu.address.model.person.Person; +import seedu.address.model.tutee.Tutee; +import seedu.address.testutil.TuteeBuilder; + +//@@author ChoChihTun +/** + * A utility class containing a list of {@code Tutee} objects to be used in tests. + */ +public class TypicalTutees { + + // Manually added + public static final Tutee ALICETUTEE = new TuteeBuilder().withName("Alice Pauline") + .withAddress("123, Jurong West Ave 6, #08-111").withEmail("alice@gmail.com") + .withPhone("85355255").withSubject("mathematics").withGrade("C+").withEducationLevel("secondary") + .withSchool("fengshan secondary school").withTags("friends").build(); + + public static final Tutee CARLTUTEE = new TuteeBuilder().withName("Carl Kurz").withPhone("95352563") + .withEmail("heinz@example.com").withAddress("wall street").withSubject("history").withGrade("B") + .withEducationLevel("secondary").withSchool("wall street high school").build(); + + public static final Tutee HOONTUTEE = new TuteeBuilder().withName("Hoon Meier").withPhone("8482424") + .withEmail("stefan@example.com").withAddress("little india").withSubject("economics").withGrade("A1") + .withEducationLevel("secondary").withSchool("changi secondary school").build(); + + public static final Tutee IDATUTEE = new TuteeBuilder().withName("Ida Mueller").withPhone("8482131") + .withEmail("hans@example.com").withAddress("chicago ave").withSubject("english").withGrade("B3") + .withEducationLevel("secondary").withSchool("tanjong katong secondary school").build(); + + // Manually added - Tutee's details found in {@code CommandTestUtil} + public static final Tutee AMYTUTEE = new TuteeBuilder().withName(VALID_NAME_AMY).withPhone(VALID_PHONE_AMY) + .withEmail(VALID_EMAIL_AMY).withAddress(VALID_ADDRESS_AMY).withSubject(VALID_SUBJECT_AMY) + .withGrade(VALID_GRADE_AMY).withEducationLevel(VALID_EDUCATION_LEVEL_AMY).withSchool(VALID_SCHOOL_AMY) + .withTags(VALID_TAG_FRIEND).build(); + + public static final Tutee BOBTUTEE = new TuteeBuilder().withName(VALID_NAME_BOB).withPhone(VALID_PHONE_BOB) + .withEmail(VALID_EMAIL_BOB).withAddress(VALID_ADDRESS_BOB).withSubject(VALID_SUBJECT_BOB) + .withGrade(VALID_GRADE_BOB).withEducationLevel(VALID_EDUCATION_LEVEL_BOB).withSchool(VALID_SCHOOL_BOB) + .withTags(VALID_TAG_HUSBAND, VALID_TAG_FRIEND).build(); + + private TypicalTutees() {} // prevents instantiation + + public static List getTypicalPersonsAndTutees() { + + return new ArrayList<>(Arrays.asList(ALICETUTEE, DANIEL, AMYTUTEE, BOBTUTEE)); + } +} diff --git a/src/test/java/seedu/address/ui/BrowserPanelTest.java b/src/test/java/seedu/address/ui/BrowserPanelTest.java deleted file mode 100644 index 48aab940f8a8..000000000000 --- a/src/test/java/seedu/address/ui/BrowserPanelTest.java +++ /dev/null @@ -1,48 +0,0 @@ -package seedu.address.ui; - -import static guitests.guihandles.WebViewUtil.waitUntilBrowserLoaded; -import static org.junit.Assert.assertEquals; -import static seedu.address.testutil.EventsUtil.postNow; -import static seedu.address.testutil.TypicalPersons.ALICE; -import static seedu.address.ui.BrowserPanel.DEFAULT_PAGE; -import static seedu.address.ui.UiPart.FXML_FILE_FOLDER; - -import java.net.URL; - -import org.junit.Before; -import org.junit.Test; - -import guitests.guihandles.BrowserPanelHandle; -import seedu.address.MainApp; -import seedu.address.commons.events.ui.PersonPanelSelectionChangedEvent; - -public class BrowserPanelTest extends GuiUnitTest { - private PersonPanelSelectionChangedEvent selectionChangedEventStub; - - private BrowserPanel browserPanel; - private BrowserPanelHandle browserPanelHandle; - - @Before - public void setUp() { - selectionChangedEventStub = new PersonPanelSelectionChangedEvent(new PersonCard(ALICE, 0)); - - guiRobot.interact(() -> browserPanel = new BrowserPanel()); - uiPartRule.setUiPart(browserPanel); - - browserPanelHandle = new BrowserPanelHandle(browserPanel.getRoot()); - } - - @Test - public void display() throws Exception { - // default web page - URL expectedDefaultPageUrl = MainApp.class.getResource(FXML_FILE_FOLDER + DEFAULT_PAGE); - assertEquals(expectedDefaultPageUrl, browserPanelHandle.getLoadedUrl()); - - // associated web page of a person - postNow(selectionChangedEventStub); - URL expectedPersonUrl = new URL(BrowserPanel.SEARCH_PAGE_URL + ALICE.getName().fullName.replaceAll(" ", "%20")); - - waitUntilBrowserLoaded(browserPanelHandle); - assertEquals(expectedPersonUrl, browserPanelHandle.getLoadedUrl()); - } -} diff --git a/src/test/java/seedu/address/ui/CalendarPanelTest.java b/src/test/java/seedu/address/ui/CalendarPanelTest.java new file mode 100644 index 000000000000..8a14f6255392 --- /dev/null +++ b/src/test/java/seedu/address/ui/CalendarPanelTest.java @@ -0,0 +1,51 @@ +package seedu.address.ui; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +import org.junit.Before; +import org.junit.Test; + +import guitests.guihandles.CalendarPanelHandle; + +//@@author ChoChihTun +public class CalendarPanelTest extends GuiUnitTest { + + private CalendarPanel calendarPanel; + private CalendarPanelHandle calendarPanelHandle; + + @Before + public void setUp() { + calendarPanel = new CalendarPanel(); + guiRobot.interact(() -> calendarPanel = new CalendarPanel()); + uiPartRule.setUiPart(calendarPanel); + + calendarPanelHandle = new CalendarPanelHandle(calendarPanel.getRoot()); + } + + @Test + public void display() { + // calendar view page is not null + assertNotNull(calendarPanel.getRoot()); + + // default view page of calendar + assertEquals(calendarPanel.getRoot().getSelectedPage(), calendarPanelHandle.getDefaultCalendarViewPage()); + + // view page changes to week + CalendarPanel.changeViewPage('w'); + assertEquals(calendarPanelHandle.getCurrentCalendarViewPage(), calendarPanelHandle.getWeekViewPage()); + + // view page changes to month + CalendarPanel.changeViewPage('m'); + assertEquals(calendarPanelHandle.getCurrentCalendarViewPage(), calendarPanelHandle.getMonthViewPage()); + + // view page changes to year + CalendarPanel.changeViewPage('y'); + assertEquals(calendarPanelHandle.getCurrentCalendarViewPage(), calendarPanelHandle.getYearViewPage()); + + // view page changes to day (default) + CalendarPanel.changeViewPage('d'); + assertEquals(calendarPanelHandle.getCurrentCalendarViewPage(), + calendarPanelHandle.getDefaultCalendarViewPage()); + } +} diff --git a/src/test/java/seedu/address/ui/CommandBoxTest.java b/src/test/java/seedu/address/ui/CommandBoxTest.java index f72304570a7a..255dc2d722de 100644 --- a/src/test/java/seedu/address/ui/CommandBoxTest.java +++ b/src/test/java/seedu/address/ui/CommandBoxTest.java @@ -5,6 +5,7 @@ import java.util.ArrayList; import org.junit.Before; +import org.junit.BeforeClass; import org.junit.Test; import guitests.guihandles.CommandBoxHandle; @@ -14,6 +15,7 @@ import seedu.address.logic.commands.ListCommand; import seedu.address.model.Model; import seedu.address.model.ModelManager; +import systemtests.SystemTestSetupHelper; public class CommandBoxTest extends GuiUnitTest { @@ -25,6 +27,11 @@ public class CommandBoxTest extends GuiUnitTest { private CommandBoxHandle commandBoxHandle; + @BeforeClass + public static void setupBeforeClass() { + SystemTestSetupHelper.initialize(); + } + @Before public void setUp() { Model model = new ModelManager(); diff --git a/src/test/java/seedu/address/ui/PersonListPanelTest.java b/src/test/java/seedu/address/ui/PersonListPanelTest.java index 906ec2bebc03..bda7249116f9 100644 --- a/src/test/java/seedu/address/ui/PersonListPanelTest.java +++ b/src/test/java/seedu/address/ui/PersonListPanelTest.java @@ -3,7 +3,7 @@ import static org.junit.Assert.assertEquals; import static seedu.address.testutil.EventsUtil.postNow; import static seedu.address.testutil.TypicalIndexes.INDEX_SECOND_PERSON; -import static seedu.address.testutil.TypicalPersons.getTypicalPersons; +import static seedu.address.testutil.typicaladdressbook.TypicalPersons.getTypicalPersons; import static seedu.address.ui.testutil.GuiTestAssert.assertCardDisplaysPerson; import static seedu.address.ui.testutil.GuiTestAssert.assertCardEquals; diff --git a/src/test/java/seedu/address/ui/testutil/GuiTestAssert.java b/src/test/java/seedu/address/ui/testutil/GuiTestAssert.java index d21cc2fb3739..fecf9f1d0485 100644 --- a/src/test/java/seedu/address/ui/testutil/GuiTestAssert.java +++ b/src/test/java/seedu/address/ui/testutil/GuiTestAssert.java @@ -8,6 +8,9 @@ import guitests.guihandles.PersonCardHandle; import guitests.guihandles.PersonListPanelHandle; import guitests.guihandles.ResultDisplayHandle; +import guitests.guihandles.TaskCardHandle; +import guitests.guihandles.TaskListPanelHandle; +import seedu.address.model.Task; import seedu.address.model.person.Person; /** @@ -26,6 +29,15 @@ public static void assertCardEquals(PersonCardHandle expectedCard, PersonCardHan assertEquals(expectedCard.getTags(), actualCard.getTags()); } + /** + * Asserts that {@code actualCard} displays the same values as {@code expectedCard}. + */ + public static void assertCardEquals(TaskCardHandle expectedCard, TaskCardHandle actualCard) { + assertEquals(expectedCard.getDescription(), actualCard.getDescription()); + assertEquals(expectedCard.getDuration(), actualCard.getDuration()); + assertEquals(expectedCard.getDateAndTime(), actualCard.getDateAndTime()); + } + /** * Asserts that {@code actualCard} displays the details of {@code expectedPerson}. */ @@ -38,6 +50,16 @@ public static void assertCardDisplaysPerson(Person expectedPerson, PersonCardHan actualCard.getTags()); } + /** + * Asserts that {@code actualCard} displays the details of {@code expectedPerson}. + */ + public static void assertCardDisplaysTask(Task expectedTask, TaskCardHandle actualCard) { + assertEquals(expectedTask.getDescription(), actualCard.getDescription()); + assertEquals(expectedTask.getDuration(), actualCard.getDuration()); + assertEquals(expectedTask.getTaskDateTime().toString(), actualCard.getDateAndTime().toString()); + } + + /** * Asserts that the list in {@code personListPanelHandle} displays the details of {@code persons} correctly and * in the correct order. @@ -48,6 +70,16 @@ public static void assertListMatching(PersonListPanelHandle personListPanelHandl } } + /** + * Asserts that the list in {@code taskListPanelHandle} displays the details of {@code tasks} correctly and + * in the correct order. + */ + public static void assertListMatching(TaskListPanelHandle taskListPanelHandle, Task... tasks) { + for (int i = 0; i < tasks.length; i++) { + assertCardDisplaysTask(tasks[i], taskListPanelHandle.getTaskCardHandle(i)); + } + } + /** * Asserts that the list in {@code personListPanelHandle} displays the details of {@code persons} correctly and * in the correct order. @@ -56,6 +88,14 @@ public static void assertListMatching(PersonListPanelHandle personListPanelHandl assertListMatching(personListPanelHandle, persons.toArray(new Person[0])); } + /** + * Asserts that the list in {@code taskListPanelHandle} displays the details of {@code tasks} correctly and + * in the correct order. + */ + public static void assertListMatching(TaskListPanelHandle taskListPanelHandle, List tasks) { + assertListMatching(taskListPanelHandle, tasks.toArray(new Task[0])); + } + /** * Asserts the size of the list in {@code personListPanelHandle} equals to {@code size}. */ @@ -64,6 +104,14 @@ public static void assertListSize(PersonListPanelHandle personListPanelHandle, i assertEquals(size, numberOfPeople); } + /** + * Asserts the size of the list in {@code personListPanelHandle} equals to {@code size}. + */ + public static void assertListSize(TaskListPanelHandle taskListPanelHandle, int size) { + int numberOfTasks = taskListPanelHandle.getListSize(); + assertEquals(size, numberOfTasks); + } + /** * Asserts the message shown in {@code resultDisplayHandle} equals to {@code expected}. */ @@ -71,3 +119,4 @@ public static void assertResultMessage(ResultDisplayHandle resultDisplayHandle, assertEquals(expected, resultDisplayHandle.getText()); } } + diff --git a/src/test/java/systemtests/AddCommandSystemTest.java b/src/test/java/systemtests/AddCommandSystemTest.java index 3254b60154c4..5c753e65094c 100644 --- a/src/test/java/systemtests/AddCommandSystemTest.java +++ b/src/test/java/systemtests/AddCommandSystemTest.java @@ -26,18 +26,16 @@ import static seedu.address.logic.commands.CommandTestUtil.VALID_PHONE_BOB; import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_FRIEND; import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; -import static seedu.address.testutil.TypicalPersons.ALICE; -import static seedu.address.testutil.TypicalPersons.AMY; -import static seedu.address.testutil.TypicalPersons.BOB; -import static seedu.address.testutil.TypicalPersons.CARL; -import static seedu.address.testutil.TypicalPersons.HOON; -import static seedu.address.testutil.TypicalPersons.IDA; -import static seedu.address.testutil.TypicalPersons.KEYWORD_MATCHING_MEIER; +import static seedu.address.testutil.typicaladdressbook.TypicalPersons.ALICE; +import static seedu.address.testutil.typicaladdressbook.TypicalPersons.AMY; +import static seedu.address.testutil.typicaladdressbook.TypicalPersons.BOB; +import static seedu.address.testutil.typicaladdressbook.TypicalPersons.HOON; +import static seedu.address.testutil.typicaladdressbook.TypicalPersons.IDA; +import static seedu.address.testutil.typicaladdressbook.TypicalPersons.KEYWORD_MATCHING_MEIER; import org.junit.Test; import seedu.address.commons.core.Messages; -import seedu.address.commons.core.index.Index; import seedu.address.logic.commands.AddCommand; import seedu.address.logic.commands.RedoCommand; import seedu.address.logic.commands.UndoCommand; @@ -126,12 +124,6 @@ public void add() throws Exception { showPersonsWithName(KEYWORD_MATCHING_MEIER); assertCommandSuccess(IDA); - /* ------------------------ Perform add operation while a person card is selected --------------------------- */ - - /* Case: selects first card in the person list, add a person -> added, card selection remains unchanged */ - selectPerson(Index.fromOneBased(1)); - assertCommandSuccess(CARL); - /* ----------------------------------- Perform invalid add operations --------------------------------------- */ /* Case: add a duplicate person -> rejected */ @@ -187,6 +179,134 @@ public void add() throws Exception { assertCommandFailure(command, Tag.MESSAGE_TAG_CONSTRAINTS); } + @Test + public void addAlias() throws Exception { + Model model = getModel(); + + /* ------------------------ Perform add operations on the shown unfiltered list ----------------------------- */ + + + /* Case: add a person without tags to a non-empty address book, command with leading spaces and trailing spaces + * -> added + */ + Person toAdd = AMY; + String command = " " + AddCommand.COMMAND_ALIAS + " " + NAME_DESC_AMY + " " + PHONE_DESC_AMY + " " + + EMAIL_DESC_AMY + " " + ADDRESS_DESC_AMY + " " + TAG_DESC_FRIEND + " "; + assertCommandSuccess(command, toAdd); + + /* Case: undo adding Amy to the list -> Amy deleted */ + command = UndoCommand.COMMAND_ALIAS; + String expectedResultMessage = UndoCommand.MESSAGE_SUCCESS; + assertCommandSuccess(command, model, expectedResultMessage); + + /* Case: redo adding Amy to the list -> Amy added again */ + command = RedoCommand.COMMAND_ALIAS; + model.addPerson(toAdd); + expectedResultMessage = RedoCommand.MESSAGE_SUCCESS; + assertCommandSuccess(command, model, expectedResultMessage); + + /* Case: add a person with all fields same as another person in the address book except name -> added */ + toAdd = new PersonBuilder().withName(VALID_NAME_BOB).withPhone(VALID_PHONE_AMY).withEmail(VALID_EMAIL_AMY) + .withAddress(VALID_ADDRESS_AMY).withTags(VALID_TAG_FRIEND).build(); + command = AddCommand.COMMAND_ALIAS + NAME_DESC_BOB + PHONE_DESC_AMY + EMAIL_DESC_AMY + ADDRESS_DESC_AMY + + TAG_DESC_FRIEND; + assertCommandSuccess(command, toAdd); + + /* Case: add a person with all fields same as another person in the address book except phone -> added */ + toAdd = new PersonBuilder().withName(VALID_NAME_AMY).withPhone(VALID_PHONE_BOB).withEmail(VALID_EMAIL_AMY) + .withAddress(VALID_ADDRESS_AMY).withTags(VALID_TAG_FRIEND).build(); + command = AddCommand.COMMAND_ALIAS + NAME_DESC_AMY + PHONE_DESC_BOB + EMAIL_DESC_AMY + ADDRESS_DESC_AMY + + TAG_DESC_FRIEND; + assertCommandSuccess(command, toAdd); + + /* Case: add a person with all fields same as another person in the address book except email -> added */ + toAdd = new PersonBuilder().withName(VALID_NAME_AMY).withPhone(VALID_PHONE_AMY).withEmail(VALID_EMAIL_BOB) + .withAddress(VALID_ADDRESS_AMY).withTags(VALID_TAG_FRIEND).build(); + command = AddCommand.COMMAND_ALIAS + NAME_DESC_AMY + PHONE_DESC_AMY + EMAIL_DESC_BOB + ADDRESS_DESC_AMY + + TAG_DESC_FRIEND; + assertCommandSuccess(command, toAdd); + + /* Case: add a person with all fields same as another person in the address book except address -> added */ + toAdd = new PersonBuilder().withName(VALID_NAME_AMY).withPhone(VALID_PHONE_AMY).withEmail(VALID_EMAIL_AMY) + .withAddress(VALID_ADDRESS_BOB).withTags(VALID_TAG_FRIEND).build(); + command = AddCommand.COMMAND_ALIAS + NAME_DESC_AMY + PHONE_DESC_AMY + EMAIL_DESC_AMY + ADDRESS_DESC_BOB + + TAG_DESC_FRIEND; + assertCommandSuccess(command, toAdd); + + /* Case: add to empty address book -> added */ + deleteAllPersons(); + assertCommandSuccess(ALICE); + + /* Case: add a person with tags, command with parameters in random order -> added */ + toAdd = BOB; + command = AddCommand.COMMAND_ALIAS + TAG_DESC_FRIEND + PHONE_DESC_BOB + ADDRESS_DESC_BOB + NAME_DESC_BOB + + TAG_DESC_HUSBAND + EMAIL_DESC_BOB; + assertCommandSuccess(command, toAdd); + + /* Case: add a person, missing tags -> added */ + assertCommandSuccess(HOON); + + /* -------------------------- Perform add operation on the shown filtered list ------------------------------ */ + + /* Case: filters the person list before adding -> added */ + showPersonsWithName(KEYWORD_MATCHING_MEIER); + assertCommandSuccess(IDA); + + /* ----------------------------------- Perform invalid add operations --------------------------------------- */ + + /* Case: add a duplicate person -> rejected */ + command = PersonUtil.getAddCommand(HOON); + assertCommandFailure(command, AddCommand.MESSAGE_DUPLICATE_PERSON); + + /* Case: add a duplicate person except with different tags -> rejected */ + // "friends" is an existing tag used in the default model, see TypicalPersons#ALICE + // This test will fail if a new tag that is not in the model is used, see the bug documented in + // AddressBook#addPerson(Person) + command = PersonUtil.getAddCommand(HOON) + " " + PREFIX_TAG.getPrefix() + "friends"; + assertCommandFailure(command, AddCommand.MESSAGE_DUPLICATE_PERSON); + + /* Case: missing name -> rejected */ + command = AddCommand.COMMAND_ALIAS + PHONE_DESC_AMY + EMAIL_DESC_AMY + ADDRESS_DESC_AMY; + assertCommandFailure(command, String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddCommand.MESSAGE_USAGE)); + + /* Case: missing phone -> rejected */ + command = AddCommand.COMMAND_ALIAS + NAME_DESC_AMY + EMAIL_DESC_AMY + ADDRESS_DESC_AMY; + assertCommandFailure(command, String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddCommand.MESSAGE_USAGE)); + + /* Case: missing email -> rejected */ + command = AddCommand.COMMAND_ALIAS + NAME_DESC_AMY + PHONE_DESC_AMY + ADDRESS_DESC_AMY; + assertCommandFailure(command, String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddCommand.MESSAGE_USAGE)); + + /* Case: missing address -> rejected */ + command = AddCommand.COMMAND_ALIAS + NAME_DESC_AMY + PHONE_DESC_AMY + EMAIL_DESC_AMY; + assertCommandFailure(command, String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddCommand.MESSAGE_USAGE)); + + /* Case: invalid keyword -> rejected */ + command = "adds " + PersonUtil.getPersonDetails(toAdd); + assertCommandFailure(command, Messages.MESSAGE_UNKNOWN_COMMAND); + + /* Case: invalid name -> rejected */ + command = AddCommand.COMMAND_ALIAS + INVALID_NAME_DESC + PHONE_DESC_AMY + EMAIL_DESC_AMY + ADDRESS_DESC_AMY; + assertCommandFailure(command, Name.MESSAGE_NAME_CONSTRAINTS); + + /* Case: invalid phone -> rejected */ + command = AddCommand.COMMAND_ALIAS + NAME_DESC_AMY + INVALID_PHONE_DESC + EMAIL_DESC_AMY + ADDRESS_DESC_AMY; + assertCommandFailure(command, Phone.MESSAGE_PHONE_CONSTRAINTS); + + /* Case: invalid email -> rejected */ + command = AddCommand.COMMAND_ALIAS + NAME_DESC_AMY + PHONE_DESC_AMY + INVALID_EMAIL_DESC + ADDRESS_DESC_AMY; + assertCommandFailure(command, Email.MESSAGE_EMAIL_CONSTRAINTS); + + /* Case: invalid address -> rejected */ + command = AddCommand.COMMAND_ALIAS + NAME_DESC_AMY + PHONE_DESC_AMY + EMAIL_DESC_AMY + INVALID_ADDRESS_DESC; + assertCommandFailure(command, Address.MESSAGE_ADDRESS_CONSTRAINTS); + + /* Case: invalid tag -> rejected */ + command = AddCommand.COMMAND_ALIAS + NAME_DESC_AMY + PHONE_DESC_AMY + EMAIL_DESC_AMY + ADDRESS_DESC_AMY + + INVALID_TAG_DESC; + assertCommandFailure(command, Tag.MESSAGE_TAG_CONSTRAINTS); + } + /** * Executes the {@code AddCommand} that adds {@code toAdd} to the model and asserts that the,
* 1. Command box displays an empty string.
diff --git a/src/test/java/systemtests/AddTuteeCommandSystemTest.java b/src/test/java/systemtests/AddTuteeCommandSystemTest.java new file mode 100644 index 000000000000..fbc047a86cfc --- /dev/null +++ b/src/test/java/systemtests/AddTuteeCommandSystemTest.java @@ -0,0 +1,332 @@ +package systemtests; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.commands.CommandTestUtil.ADDRESS_DESC_AMY; +import static seedu.address.logic.commands.CommandTestUtil.ADDRESS_DESC_BOB; +import static seedu.address.logic.commands.CommandTestUtil.EDUCATION_LEVEL_DESC_AMY; +import static seedu.address.logic.commands.CommandTestUtil.EDUCATION_LEVEL_DESC_BOB; +import static seedu.address.logic.commands.CommandTestUtil.EMAIL_DESC_AMY; +import static seedu.address.logic.commands.CommandTestUtil.EMAIL_DESC_BOB; +import static seedu.address.logic.commands.CommandTestUtil.GRADE_DESC_AMY; +import static seedu.address.logic.commands.CommandTestUtil.GRADE_DESC_BOB; +import static seedu.address.logic.commands.CommandTestUtil.INVALID_ADDRESS_DESC; +import static seedu.address.logic.commands.CommandTestUtil.INVALID_EDUCATION_LEVEL; +import static seedu.address.logic.commands.CommandTestUtil.INVALID_EMAIL_DESC; +import static seedu.address.logic.commands.CommandTestUtil.INVALID_GRADE_DESC; +import static seedu.address.logic.commands.CommandTestUtil.INVALID_NAME_DESC; +import static seedu.address.logic.commands.CommandTestUtil.INVALID_PHONE_DESC; +import static seedu.address.logic.commands.CommandTestUtil.INVALID_SCHOOL; +import static seedu.address.logic.commands.CommandTestUtil.INVALID_SUBJECT_DESC; +import static seedu.address.logic.commands.CommandTestUtil.INVALID_TAG_DESC; +import static seedu.address.logic.commands.CommandTestUtil.NAME_DESC_AMY; +import static seedu.address.logic.commands.CommandTestUtil.NAME_DESC_BOB; +import static seedu.address.logic.commands.CommandTestUtil.PHONE_DESC_AMY; +import static seedu.address.logic.commands.CommandTestUtil.PHONE_DESC_BOB; +import static seedu.address.logic.commands.CommandTestUtil.SCHOOL_DESC_AMY; +import static seedu.address.logic.commands.CommandTestUtil.SCHOOL_DESC_BOB; +import static seedu.address.logic.commands.CommandTestUtil.SUBJECT_DESC_AMY; +import static seedu.address.logic.commands.CommandTestUtil.SUBJECT_DESC_BOB; +import static seedu.address.logic.commands.CommandTestUtil.TAG_DESC_FRIEND; +import static seedu.address.logic.commands.CommandTestUtil.TAG_DESC_HUSBAND; +import static seedu.address.logic.commands.CommandTestUtil.VALID_ADDRESS_AMY; +import static seedu.address.logic.commands.CommandTestUtil.VALID_ADDRESS_BOB; +import static seedu.address.logic.commands.CommandTestUtil.VALID_EDUCATION_LEVEL_AMY; +import static seedu.address.logic.commands.CommandTestUtil.VALID_EMAIL_AMY; +import static seedu.address.logic.commands.CommandTestUtil.VALID_EMAIL_BOB; +import static seedu.address.logic.commands.CommandTestUtil.VALID_GRADE_AMY; +import static seedu.address.logic.commands.CommandTestUtil.VALID_NAME_AMY; +import static seedu.address.logic.commands.CommandTestUtil.VALID_NAME_BOB; +import static seedu.address.logic.commands.CommandTestUtil.VALID_PHONE_AMY; +import static seedu.address.logic.commands.CommandTestUtil.VALID_PHONE_BOB; +import static seedu.address.logic.commands.CommandTestUtil.VALID_SCHOOL_AMY; +import static seedu.address.logic.commands.CommandTestUtil.VALID_SUBJECT_AMY; +import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_FRIEND; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; +import static seedu.address.testutil.typicaladdressbook.TypicalPersons.KEYWORD_MATCHING_MEIER; +import static seedu.address.testutil.typicaladdressbook.TypicalTutees.ALICETUTEE; +import static seedu.address.testutil.typicaladdressbook.TypicalTutees.AMYTUTEE; +import static seedu.address.testutil.typicaladdressbook.TypicalTutees.BOBTUTEE; +import static seedu.address.testutil.typicaladdressbook.TypicalTutees.HOONTUTEE; +import static seedu.address.testutil.typicaladdressbook.TypicalTutees.IDATUTEE; + +import org.junit.Test; + +import seedu.address.commons.core.Messages; +import seedu.address.logic.commands.AddTuteeCommand; +import seedu.address.logic.commands.RedoCommand; +import seedu.address.logic.commands.UndoCommand; +import seedu.address.model.Model; +import seedu.address.model.person.Address; +import seedu.address.model.person.Email; +import seedu.address.model.person.Name; +import seedu.address.model.person.Phone; +import seedu.address.model.person.exceptions.DuplicatePersonException; +import seedu.address.model.tag.Tag; +import seedu.address.model.tutee.EducationLevel; +import seedu.address.model.tutee.Grade; +import seedu.address.model.tutee.School; +import seedu.address.model.tutee.Subject; +import seedu.address.model.tutee.Tutee; +import seedu.address.testutil.TuteeBuilder; +import seedu.address.testutil.TuteeUtil; + +//@@author ChoChihTun +public class AddTuteeCommandSystemTest extends AddressBookSystemTest { + + @Test + public void addtutee() throws Exception { + Model model = getModel(); + + /* ------------------------ Perform add operations on the shown unfiltered list ----------------------------- */ + + /* Case: add a tutee without tags to a non-empty address book, command with leading spaces and trailing spaces + * -> added + */ + Tutee toAdd = AMYTUTEE; + String command = " " + AddTuteeCommand.COMMAND_WORD + " " + NAME_DESC_AMY + " " + PHONE_DESC_AMY + " " + + EMAIL_DESC_AMY + " " + ADDRESS_DESC_AMY + " " + SUBJECT_DESC_AMY + GRADE_DESC_AMY + + EDUCATION_LEVEL_DESC_AMY + SCHOOL_DESC_AMY + TAG_DESC_FRIEND + " "; + assertCommandSuccess(command, toAdd); + + /* Case: undo adding Amy to the list -> Amy deleted */ + command = UndoCommand.COMMAND_WORD; + String expectedResultMessage = UndoCommand.MESSAGE_SUCCESS; + assertCommandSuccess(command, model, expectedResultMessage); + + /* Case: redo adding Amy to the list -> Amy added again */ + command = RedoCommand.COMMAND_WORD; + model.addPerson(toAdd); + expectedResultMessage = RedoCommand.MESSAGE_SUCCESS; + assertCommandSuccess(command, model, expectedResultMessage); + + /* Case: add a tutee with all fields same as another tutee in the address book except name -> added */ + toAdd = new TuteeBuilder().withName(VALID_NAME_BOB).withPhone(VALID_PHONE_AMY).withEmail(VALID_EMAIL_AMY) + .withAddress(VALID_ADDRESS_AMY).withSubject(VALID_SUBJECT_AMY).withGrade(VALID_GRADE_AMY) + .withEducationLevel(VALID_EDUCATION_LEVEL_AMY).withSchool(VALID_SCHOOL_AMY).withTags(VALID_TAG_FRIEND) + .build(); + command = AddTuteeCommand.COMMAND_WORD + NAME_DESC_BOB + PHONE_DESC_AMY + EMAIL_DESC_AMY + ADDRESS_DESC_AMY + + SUBJECT_DESC_AMY + GRADE_DESC_AMY + EDUCATION_LEVEL_DESC_AMY + SCHOOL_DESC_AMY + TAG_DESC_FRIEND; + assertCommandSuccess(command, toAdd); + + /* Case: add a tutee with all fields same as another tutee in the address book except phone -> added */ + toAdd = new TuteeBuilder().withName(VALID_NAME_AMY).withPhone(VALID_PHONE_BOB).withEmail(VALID_EMAIL_AMY) + .withAddress(VALID_ADDRESS_AMY).withTags(VALID_TAG_FRIEND).withSubject(VALID_SUBJECT_AMY) + .withGrade(VALID_GRADE_AMY).withEducationLevel(VALID_EDUCATION_LEVEL_AMY).withSchool(VALID_SCHOOL_AMY) + .withTags(VALID_TAG_FRIEND).build(); + command = AddTuteeCommand.COMMAND_WORD + NAME_DESC_AMY + PHONE_DESC_BOB + EMAIL_DESC_AMY + ADDRESS_DESC_AMY + + SUBJECT_DESC_AMY + GRADE_DESC_AMY + EDUCATION_LEVEL_DESC_AMY + SCHOOL_DESC_AMY + TAG_DESC_FRIEND; + assertCommandSuccess(command, toAdd); + + /* Case: add a tutee with all fields same as another tutee in the address book except email -> added */ + toAdd = new TuteeBuilder().withName(VALID_NAME_AMY).withPhone(VALID_PHONE_AMY).withEmail(VALID_EMAIL_BOB) + .withAddress(VALID_ADDRESS_AMY).withSubject(VALID_SUBJECT_AMY).withGrade(VALID_GRADE_AMY) + .withEducationLevel(VALID_EDUCATION_LEVEL_AMY).withSchool(VALID_SCHOOL_AMY).withTags(VALID_TAG_FRIEND) + .withTags(VALID_TAG_FRIEND).build(); + command = AddTuteeCommand.COMMAND_WORD + NAME_DESC_AMY + PHONE_DESC_AMY + EMAIL_DESC_BOB + ADDRESS_DESC_AMY + + SUBJECT_DESC_AMY + GRADE_DESC_AMY + EDUCATION_LEVEL_DESC_AMY + SCHOOL_DESC_AMY + TAG_DESC_FRIEND; + assertCommandSuccess(command, toAdd); + + /* Case: add a tutee with all fields same as another tutee in the address book except address -> added */ + toAdd = new TuteeBuilder().withName(VALID_NAME_AMY).withPhone(VALID_PHONE_AMY).withEmail(VALID_EMAIL_AMY) + .withAddress(VALID_ADDRESS_BOB).withSubject(VALID_SUBJECT_AMY).withGrade(VALID_GRADE_AMY) + .withEducationLevel(VALID_EDUCATION_LEVEL_AMY).withSchool(VALID_SCHOOL_AMY).withTags(VALID_TAG_FRIEND) + .withTags(VALID_TAG_FRIEND).build(); + command = AddTuteeCommand.COMMAND_WORD + NAME_DESC_AMY + PHONE_DESC_AMY + EMAIL_DESC_AMY + ADDRESS_DESC_BOB + + SUBJECT_DESC_AMY + GRADE_DESC_AMY + EDUCATION_LEVEL_DESC_AMY + SCHOOL_DESC_AMY + TAG_DESC_FRIEND; + assertCommandSuccess(command, toAdd); + + /* Case: add to empty address book -> added */ + deleteAllPersons(); + assertCommandSuccess(ALICETUTEE); + + /* Case: add a tutee with tags, command with parameters in random order -> added */ + toAdd = BOBTUTEE; + command = AddTuteeCommand.COMMAND_WORD + TAG_DESC_FRIEND + PHONE_DESC_BOB + ADDRESS_DESC_BOB + NAME_DESC_BOB + + TAG_DESC_HUSBAND + EMAIL_DESC_BOB + GRADE_DESC_BOB + SUBJECT_DESC_BOB + EDUCATION_LEVEL_DESC_BOB + + SCHOOL_DESC_BOB; + assertCommandSuccess(command, toAdd); + + /* Case: add a tutee, missing tags -> added */ + assertCommandSuccess(HOONTUTEE); + + /* -------------------------- Perform add operation on the shown filtered list ------------------------------ */ + + /* Case: filters the contact list before adding -> added */ + showPersonsWithName(KEYWORD_MATCHING_MEIER); + assertCommandSuccess(IDATUTEE); + + /* ----------------------------------- Perform invalid add operations --------------------------------------- */ + + /* Case: add a duplicate tutee -> rejected */ + command = TuteeUtil.getAddTuteeCommand(HOONTUTEE); + assertCommandFailure(command, AddTuteeCommand.MESSAGE_DUPLICATE_PERSON); + + /* Case: add a duplicate tutee except with different tags -> rejected */ + // "friends" is an existing tag used in the default model, see TypicalPersons#ALICE + // This test will fail if a new tag that is not in the model is used, see the bug documented in + // AddressBook#addPerson(Person) + command = TuteeUtil.getAddTuteeCommand(HOONTUTEE) + " " + PREFIX_TAG.getPrefix() + "friends"; + assertCommandFailure(command, AddTuteeCommand.MESSAGE_DUPLICATE_PERSON); + + /* Case: missing name -> rejected */ + command = AddTuteeCommand.COMMAND_WORD + PHONE_DESC_AMY + EMAIL_DESC_AMY + ADDRESS_DESC_AMY + + SUBJECT_DESC_AMY + GRADE_DESC_AMY + EDUCATION_LEVEL_DESC_AMY + SCHOOL_DESC_AMY; + assertCommandFailure(command, String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddTuteeCommand.MESSAGE_USAGE)); + + /* Case: missing phone -> rejected */ + command = AddTuteeCommand.COMMAND_WORD + NAME_DESC_AMY + EMAIL_DESC_AMY + ADDRESS_DESC_AMY + + SUBJECT_DESC_AMY + GRADE_DESC_AMY + EDUCATION_LEVEL_DESC_AMY + SCHOOL_DESC_AMY; + assertCommandFailure(command, String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddTuteeCommand.MESSAGE_USAGE)); + + /* Case: missing email -> rejected */ + command = AddTuteeCommand.COMMAND_WORD + NAME_DESC_AMY + PHONE_DESC_AMY + ADDRESS_DESC_AMY + + SUBJECT_DESC_AMY + GRADE_DESC_AMY + EDUCATION_LEVEL_DESC_AMY + SCHOOL_DESC_AMY; + assertCommandFailure(command, String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddTuteeCommand.MESSAGE_USAGE)); + + /* Case: missing address -> rejected */ + command = AddTuteeCommand.COMMAND_WORD + NAME_DESC_AMY + PHONE_DESC_AMY + EMAIL_DESC_AMY + + SUBJECT_DESC_AMY + GRADE_DESC_AMY + EDUCATION_LEVEL_DESC_AMY + SCHOOL_DESC_AMY; + assertCommandFailure(command, String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddTuteeCommand.MESSAGE_USAGE)); + + /* Case: missing subject -> rejected */ + command = AddTuteeCommand.COMMAND_WORD + NAME_DESC_AMY + PHONE_DESC_AMY + EMAIL_DESC_AMY + + ADDRESS_DESC_AMY + GRADE_DESC_AMY + EDUCATION_LEVEL_DESC_AMY + SCHOOL_DESC_AMY; + assertCommandFailure(command, String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddTuteeCommand.MESSAGE_USAGE)); + + /* Case: missing grade -> rejected */ + command = AddTuteeCommand.COMMAND_WORD + NAME_DESC_AMY + PHONE_DESC_AMY + EMAIL_DESC_AMY + + ADDRESS_DESC_AMY + GRADE_DESC_AMY + EDUCATION_LEVEL_DESC_AMY + SCHOOL_DESC_AMY; + assertCommandFailure(command, String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddTuteeCommand.MESSAGE_USAGE)); + + /* Case: missing education level -> rejected */ + command = AddTuteeCommand.COMMAND_WORD + NAME_DESC_AMY + PHONE_DESC_AMY + EMAIL_DESC_AMY + + ADDRESS_DESC_AMY + SUBJECT_DESC_AMY + GRADE_DESC_AMY + SCHOOL_DESC_AMY; + assertCommandFailure(command, String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddTuteeCommand.MESSAGE_USAGE)); + + /* Case: missing school -> rejected */ + command = AddTuteeCommand.COMMAND_WORD + NAME_DESC_AMY + PHONE_DESC_AMY + EMAIL_DESC_AMY + + ADDRESS_DESC_AMY + SUBJECT_DESC_AMY + GRADE_DESC_AMY + EDUCATION_LEVEL_DESC_AMY; + assertCommandFailure(command, String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddTuteeCommand.MESSAGE_USAGE)); + + /* Case: invalid keyword -> rejected */ + command = "addtutees " + TuteeUtil.getTuteeDetails(toAdd); + assertCommandFailure(command, Messages.MESSAGE_UNKNOWN_COMMAND); + + /* Case: invalid name -> rejected */ + command = AddTuteeCommand.COMMAND_WORD + INVALID_NAME_DESC + PHONE_DESC_AMY + EMAIL_DESC_AMY + ADDRESS_DESC_AMY + + SUBJECT_DESC_AMY + GRADE_DESC_AMY + EDUCATION_LEVEL_DESC_AMY + SCHOOL_DESC_AMY; + assertCommandFailure(command, Name.MESSAGE_NAME_CONSTRAINTS); + + /* Case: invalid phone -> rejected */ + command = AddTuteeCommand.COMMAND_WORD + NAME_DESC_AMY + INVALID_PHONE_DESC + EMAIL_DESC_AMY + ADDRESS_DESC_AMY + + SUBJECT_DESC_AMY + GRADE_DESC_AMY + EDUCATION_LEVEL_DESC_AMY + SCHOOL_DESC_AMY; + assertCommandFailure(command, Phone.MESSAGE_PHONE_CONSTRAINTS); + + /* Case: invalid email -> rejected */ + command = AddTuteeCommand.COMMAND_WORD + NAME_DESC_AMY + PHONE_DESC_AMY + INVALID_EMAIL_DESC + ADDRESS_DESC_AMY + + SUBJECT_DESC_AMY + GRADE_DESC_AMY + EDUCATION_LEVEL_DESC_AMY + SCHOOL_DESC_AMY; + assertCommandFailure(command, Email.MESSAGE_EMAIL_CONSTRAINTS); + + /* Case: invalid address -> rejected */ + command = AddTuteeCommand.COMMAND_WORD + NAME_DESC_AMY + PHONE_DESC_AMY + EMAIL_DESC_AMY + INVALID_ADDRESS_DESC + + SUBJECT_DESC_AMY + GRADE_DESC_AMY + EDUCATION_LEVEL_DESC_AMY + SCHOOL_DESC_AMY; + assertCommandFailure(command, Address.MESSAGE_ADDRESS_CONSTRAINTS); + + /* Case: invalid tag -> rejected */ + command = AddTuteeCommand.COMMAND_WORD + NAME_DESC_AMY + PHONE_DESC_AMY + EMAIL_DESC_AMY + ADDRESS_DESC_AMY + + SUBJECT_DESC_AMY + GRADE_DESC_AMY + EDUCATION_LEVEL_DESC_AMY + SCHOOL_DESC_AMY + INVALID_TAG_DESC; + assertCommandFailure(command, Tag.MESSAGE_TAG_CONSTRAINTS); + + /* Case: invalid subject -> rejected */ + command = AddTuteeCommand.COMMAND_WORD + NAME_DESC_AMY + PHONE_DESC_AMY + EMAIL_DESC_AMY + ADDRESS_DESC_AMY + + INVALID_SUBJECT_DESC + GRADE_DESC_AMY + EDUCATION_LEVEL_DESC_AMY + SCHOOL_DESC_AMY + INVALID_TAG_DESC; + assertCommandFailure(command, Subject.MESSAGE_SUBJECT_CONSTRAINTS); + + /* Case: invalid grade -> rejected */ + command = AddTuteeCommand.COMMAND_WORD + NAME_DESC_AMY + PHONE_DESC_AMY + EMAIL_DESC_AMY + ADDRESS_DESC_AMY + + SUBJECT_DESC_AMY + INVALID_GRADE_DESC + EDUCATION_LEVEL_DESC_AMY + SCHOOL_DESC_AMY + INVALID_TAG_DESC; + assertCommandFailure(command, Grade.MESSAGE_GRADE_CONSTRAINTS); + + /* Case: invalid education level -> rejected */ + command = AddTuteeCommand.COMMAND_WORD + NAME_DESC_AMY + PHONE_DESC_AMY + EMAIL_DESC_AMY + ADDRESS_DESC_AMY + + SUBJECT_DESC_AMY + GRADE_DESC_AMY + INVALID_EDUCATION_LEVEL + SCHOOL_DESC_AMY + INVALID_TAG_DESC; + assertCommandFailure(command, EducationLevel.MESSAGE_EDUCATION_LEVEL_CONSTRAINTS); + + /* Case: invalid school -> rejected */ + command = AddTuteeCommand.COMMAND_WORD + NAME_DESC_AMY + PHONE_DESC_AMY + EMAIL_DESC_AMY + ADDRESS_DESC_AMY + + SUBJECT_DESC_AMY + GRADE_DESC_AMY + EDUCATION_LEVEL_DESC_AMY + INVALID_SCHOOL + INVALID_TAG_DESC; + assertCommandFailure(command, School.MESSAGE_SCHOOL_CONSTRAINTS); + } + /** + * Executes the {@code AddTuteeCommand} that adds {@code toAdd} to the model and asserts that the,
+ * 1. Command box displays an empty string.
+ * 2. Command box has the default style class.
+ * 3. Result display box displays the success message of executing {@code AddTuteeCommand} with the details of + * {@code toAdd}.
+ * 4. {@code Model}, {@code Storage} and {@code PersonListPanel} equal to the corresponding components in + * the current model added with {@code toAdd}.
+ * 5. Browser url and selected card remain unchanged.
+ * 6. Status bar's sync status changes.
+ * Verifications 1, 3 and 4 are performed by + * {@code AddressBookSystemTest#assertApplicationDisplaysExpected(String, String, Model)}.
+ * @see AddressBookSystemTest#assertApplicationDisplaysExpected(String, String, Model) + */ + private void assertCommandSuccess(Tutee toAdd) { + assertCommandSuccess(TuteeUtil.getAddTuteeCommand(toAdd), toAdd); + } + + /** + * Performs the same verification as {@code assertCommandSuccess(Tutee)}. Executes {@code command} + * instead. + * @see AddTuteeCommandSystemTest#assertCommandSuccess(Tutee) + */ + private void assertCommandSuccess(String command, Tutee toAdd) { + Model expectedModel = getModel(); + try { + expectedModel.addPerson(toAdd); + } catch (DuplicatePersonException dpe) { + throw new IllegalArgumentException("toAdd already exists in the model."); + } + String expectedResultMessage = String.format(AddTuteeCommand.MESSAGE_SUCCESS, toAdd); + + assertCommandSuccess(command, expectedModel, expectedResultMessage); + } + + /** + * Performs the same verification as {@code assertCommandSuccess(String, Tutee)} except asserts that + * the,
+ * 1. Result display box displays {@code expectedResultMessage}.
+ * 2. {@code Model}, {@code Storage} and {@code PersonListPanel} equal to the corresponding components in + * {@code expectedModel}.
+ * @see AddTuteeCommandSystemTest#assertCommandSuccess(String, Tutee) + */ + private void assertCommandSuccess(String command, Model expectedModel, String expectedResultMessage) { + executeCommand(command); + assertApplicationDisplaysExpected("", expectedResultMessage, expectedModel); + assertSelectedCardUnchanged(); + assertCommandBoxShowsDefaultStyle(); + assertStatusBarUnchangedExceptSyncStatus(); + } + + /** + * Executes {@code command} and asserts that the,
+ * 1. Command box displays {@code command}.
+ * 2. Command box has the error style class.
+ * 3. Result display box displays {@code expectedResultMessage}.
+ * 4. {@code Model}, {@code Storage} and {@code PersonListPanel} remain unchanged.
+ * 5. Browser url, selected card and status bar remain unchanged.
+ * Verifications 1, 3 and 4 are performed by + * {@code AddressBookSystemTest#assertApplicationDisplaysExpected(String, String, Model)}.
+ * @see AddressBookSystemTest#assertApplicationDisplaysExpected(String, String, Model) + */ + private void assertCommandFailure(String command, String expectedResultMessage) { + Model expectedModel = getModel(); + + executeCommand(command); + assertApplicationDisplaysExpected(command, expectedResultMessage, expectedModel); + assertSelectedCardUnchanged(); + assertCommandBoxShowsErrorStyle(); + assertStatusBarUnchanged(); + } + +} diff --git a/src/test/java/systemtests/AddressBookSystemTest.java b/src/test/java/systemtests/AddressBookSystemTest.java index 97cdf96d65b8..3a847c45b569 100644 --- a/src/test/java/systemtests/AddressBookSystemTest.java +++ b/src/test/java/systemtests/AddressBookSystemTest.java @@ -1,17 +1,13 @@ package systemtests; -import static guitests.guihandles.WebViewUtil.waitUntilBrowserLoaded; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; -import static seedu.address.ui.BrowserPanel.DEFAULT_PAGE; +import static seedu.address.model.person.PersonSortUtil.CATEGORY_NAME; import static seedu.address.ui.StatusBarFooter.SYNC_STATUS_INITIAL; import static seedu.address.ui.StatusBarFooter.SYNC_STATUS_UPDATED; -import static seedu.address.ui.UiPart.FXML_FILE_FOLDER; import static seedu.address.ui.testutil.GuiTestAssert.assertListMatching; -import java.net.MalformedURLException; -import java.net.URL; import java.util.Arrays; import java.util.Date; import java.util.List; @@ -22,24 +18,24 @@ import org.junit.ClassRule; import guitests.guihandles.BrowserPanelHandle; +import guitests.guihandles.CalendarPanelHandle; import guitests.guihandles.CommandBoxHandle; import guitests.guihandles.MainMenuHandle; import guitests.guihandles.MainWindowHandle; import guitests.guihandles.PersonListPanelHandle; import guitests.guihandles.ResultDisplayHandle; import guitests.guihandles.StatusBarFooterHandle; -import seedu.address.MainApp; import seedu.address.TestApp; import seedu.address.commons.core.EventsCenter; import seedu.address.commons.core.index.Index; import seedu.address.logic.commands.ClearCommand; -import seedu.address.logic.commands.FindCommand; +import seedu.address.logic.commands.FindPersonCommand; import seedu.address.logic.commands.ListCommand; -import seedu.address.logic.commands.SelectCommand; import seedu.address.model.AddressBook; import seedu.address.model.Model; -import seedu.address.testutil.TypicalPersons; -import seedu.address.ui.BrowserPanel; +import seedu.address.model.person.exceptions.DuplicatePersonException; +import seedu.address.model.tutee.Tutee; +import seedu.address.testutil.typicaladdressbook.TypicalAddressBookCompiler; import seedu.address.ui.CommandBox; /** @@ -69,8 +65,8 @@ public void setUp() { testApp = setupHelper.setupApplication(this::getInitialData, getDataFileLocation()); mainWindowHandle = setupHelper.setupMainWindowHandle(); - waitUntilBrowserLoaded(getBrowserPanel()); assertApplicationStartingStateIsCorrect(); + assertStartingCalendarViewPageIsCorrect(); } @After @@ -83,7 +79,7 @@ public void tearDown() throws Exception { * Returns the data to be loaded into the file in {@link #getDataFileLocation()}. */ protected AddressBook getInitialData() { - return TypicalPersons.getTypicalAddressBook(); + return TypicalAddressBookCompiler.getTypicalAddressBook1(); } /** @@ -109,8 +105,8 @@ public MainMenuHandle getMainMenu() { return mainWindowHandle.getMainMenu(); } - public BrowserPanelHandle getBrowserPanel() { - return mainWindowHandle.getBrowserPanel(); + public CalendarPanelHandle getCalendarPanel() { + return mainWindowHandle.getCalendarPanel(); } public StatusBarFooterHandle getStatusBarFooter() { @@ -133,7 +129,6 @@ protected void executeCommand(String command) { mainWindowHandle.getCommandBox().run(command); - waitUntilBrowserLoaded(getBrowserPanel()); } /** @@ -141,23 +136,23 @@ protected void executeCommand(String command) { */ protected void showAllPersons() { executeCommand(ListCommand.COMMAND_WORD); - assertEquals(getModel().getAddressBook().getPersonList().size(), getModel().getFilteredPersonList().size()); + assertEquals(getModel().getAddressBook().getPersonList().size(), + getModel().getFilteredPersonList().size()); + executeCommand(ListCommand.COMMAND_ALIAS); + assertEquals(getModel().getAddressBook().getPersonList().size(), + getModel().getFilteredPersonList().size()); } /** * Displays all persons with any parts of their names matching {@code keyword} (case-insensitive). */ protected void showPersonsWithName(String keyword) { - executeCommand(FindCommand.COMMAND_WORD + " " + keyword); - assertTrue(getModel().getFilteredPersonList().size() < getModel().getAddressBook().getPersonList().size()); - } - - /** - * Selects the person at {@code index} of the displayed list. - */ - protected void selectPerson(Index index) { - executeCommand(SelectCommand.COMMAND_WORD + " " + index.getOneBased()); - assertEquals(index.getZeroBased(), getPersonListPanel().getSelectedCardIndex()); + executeCommand(FindPersonCommand.COMMAND_WORD + " " + CATEGORY_NAME + " " + keyword); + assertTrue(getModel().getFilteredPersonList().size() + < getModel().getAddressBook().getPersonList().size()); + executeCommand(FindPersonCommand.COMMAND_ALIAS + " " + CATEGORY_NAME + " " + keyword); + assertTrue(getModel().getFilteredPersonList().size() + < getModel().getAddressBook().getPersonList().size()); } /** @@ -166,6 +161,8 @@ protected void selectPerson(Index index) { protected void deleteAllPersons() { executeCommand(ClearCommand.COMMAND_WORD); assertEquals(0, getModel().getAddressBook().getPersonList().size()); + executeCommand(ClearCommand.COMMAND_ALIAS); + assertEquals(0, getModel().getAddressBook().getPersonList().size()); } /** @@ -188,7 +185,6 @@ protected void assertApplicationDisplaysExpected(String expectedCommandInput, St */ private void rememberStates() { StatusBarFooterHandle statusBarFooterHandle = getStatusBarFooter(); - getBrowserPanel().rememberUrl(); statusBarFooterHandle.rememberSaveLocation(); statusBarFooterHandle.rememberSyncStatus(); getPersonListPanel().rememberSelectedPersonCard(); @@ -200,36 +196,23 @@ private void rememberStates() { * @see BrowserPanelHandle#isUrlChanged() */ protected void assertSelectedCardDeselected() { - assertFalse(getBrowserPanel().isUrlChanged()); assertFalse(getPersonListPanel().isAnyCardSelected()); } /** * Asserts that the browser's url is changed to display the details of the person in the person list panel at * {@code expectedSelectedCardIndex}, and only the card at {@code expectedSelectedCardIndex} is selected. - * @see BrowserPanelHandle#isUrlChanged() * @see PersonListPanelHandle#isSelectedPersonCardChanged() */ protected void assertSelectedCardChanged(Index expectedSelectedCardIndex) { - String selectedCardName = getPersonListPanel().getHandleToSelectedCard().getName(); - URL expectedUrl; - try { - expectedUrl = new URL(BrowserPanel.SEARCH_PAGE_URL + selectedCardName.replaceAll(" ", "%20")); - } catch (MalformedURLException mue) { - throw new AssertionError("URL expected to be valid."); - } - assertEquals(expectedUrl, getBrowserPanel().getLoadedUrl()); - assertEquals(expectedSelectedCardIndex.getZeroBased(), getPersonListPanel().getSelectedCardIndex()); } /** * Asserts that the browser's url and the selected card in the person list panel remain unchanged. - * @see BrowserPanelHandle#isUrlChanged() * @see PersonListPanelHandle#isSelectedPersonCardChanged() */ protected void assertSelectedCardUnchanged() { - assertFalse(getBrowserPanel().isUrlChanged()); assertFalse(getPersonListPanel().isSelectedPersonCardChanged()); } @@ -276,7 +259,6 @@ private void assertApplicationStartingStateIsCorrect() { assertEquals("", getCommandBox().getInput()); assertEquals("", getResultDisplay().getText()); assertListMatching(getPersonListPanel(), getModel().getFilteredPersonList()); - assertEquals(MainApp.class.getResource(FXML_FILE_FOLDER + DEFAULT_PAGE), getBrowserPanel().getLoadedUrl()); assertEquals("./" + testApp.getStorageSaveLocation(), getStatusBarFooter().getSaveLocation()); assertEquals(SYNC_STATUS_INITIAL, getStatusBarFooter().getSyncStatus()); } catch (Exception e) { @@ -284,10 +266,33 @@ private void assertApplicationStartingStateIsCorrect() { } } + //@@author ChoChihTun + /** + * Asserts that the starting calendar view of the application is correct. + */ + private void assertStartingCalendarViewPageIsCorrect() { + assertEquals(getCalendarPanel().getDefaultCalendarViewPage(), getCalendarPanel().getCurrentCalendarViewPage()); + } + //@@author + /** * Returns a defensive copy of the current model. */ protected Model getModel() { return testApp.getModel(); } + + //@@author yungyung04 + /** + * Adds a tutee into the current model and expected model. + */ + protected void addTutee(String command, Tutee tutee, Model expectedModel) { + try { + expectedModel.addPerson(tutee); + } catch (DuplicatePersonException dpe) { + System.out.println("a tutee with the same name exists in the expected model"); + } + executeCommand(command); + } + //@@author } diff --git a/src/test/java/systemtests/ClearCommandSystemTest.java b/src/test/java/systemtests/ClearCommandSystemTest.java index 805a59784e29..6e87e8a94a4e 100644 --- a/src/test/java/systemtests/ClearCommandSystemTest.java +++ b/src/test/java/systemtests/ClearCommandSystemTest.java @@ -1,11 +1,10 @@ package systemtests; import static seedu.address.commons.core.Messages.MESSAGE_UNKNOWN_COMMAND; -import static seedu.address.testutil.TypicalPersons.KEYWORD_MATCHING_MEIER; +import static seedu.address.testutil.typicaladdressbook.TypicalPersons.KEYWORD_MATCHING_MEIER; import org.junit.Test; -import seedu.address.commons.core.index.Index; import seedu.address.logic.commands.ClearCommand; import seedu.address.logic.commands.RedoCommand; import seedu.address.logic.commands.UndoCommand; @@ -36,12 +35,6 @@ public void clear() { assertCommandSuccess(command, expectedResultMessage, new ModelManager()); assertSelectedCardUnchanged(); - /* Case: selects first card in person list and clears address book -> cleared and no card selected */ - executeCommand(UndoCommand.COMMAND_WORD); // restores the original address book - selectPerson(Index.fromOneBased(1)); - assertCommandSuccess(ClearCommand.COMMAND_WORD); - assertSelectedCardDeselected(); - /* Case: filters the person list before clearing -> entire address book cleared */ executeCommand(UndoCommand.COMMAND_WORD); // restores the original address book showPersonsWithName(KEYWORD_MATCHING_MEIER); @@ -56,6 +49,42 @@ public void clear() { assertCommandFailure("ClEaR", MESSAGE_UNKNOWN_COMMAND); } + @Test + public void clearAlias() { + final Model defaultModel = getModel(); + + /* Case: clear non-empty address book, command with leading spaces and trailing alphanumeric characters and + * spaces -> cleared + */ + assertCommandSuccess(" " + ClearCommand.COMMAND_ALIAS + " ab12 "); + assertSelectedCardUnchanged(); + + /* Case: undo clearing address book -> original address book restored */ + String command = UndoCommand.COMMAND_ALIAS; + String expectedResultMessage = UndoCommand.MESSAGE_SUCCESS; + assertCommandSuccess(command, expectedResultMessage, defaultModel); + assertSelectedCardUnchanged(); + + /* Case: redo clearing address book -> cleared */ + command = RedoCommand.COMMAND_ALIAS; + expectedResultMessage = RedoCommand.MESSAGE_SUCCESS; + assertCommandSuccess(command, expectedResultMessage, new ModelManager()); + assertSelectedCardUnchanged(); + + /* Case: filters the person list before clearing -> entire address book cleared */ + executeCommand(UndoCommand.COMMAND_ALIAS); // restores the original address book + showPersonsWithName(KEYWORD_MATCHING_MEIER); + assertCommandSuccess(ClearCommand.COMMAND_ALIAS); + assertSelectedCardUnchanged(); + + /* Case: clear empty address book -> cleared */ + assertCommandSuccess(ClearCommand.COMMAND_ALIAS); + assertSelectedCardUnchanged(); + + /* Case: mixed case command word -> rejected */ + assertCommandFailure("ClEaR", MESSAGE_UNKNOWN_COMMAND); + } + /** * Executes {@code command} and verifies that the command box displays an empty string, the result display * box displays {@code ClearCommand#MESSAGE_SUCCESS} and the model related components equal to an empty model. diff --git a/src/test/java/systemtests/DeleteCommandSystemTest.java b/src/test/java/systemtests/DeleteCommandSystemTest.java index c0de78e4aba6..2316279a991d 100644 --- a/src/test/java/systemtests/DeleteCommandSystemTest.java +++ b/src/test/java/systemtests/DeleteCommandSystemTest.java @@ -8,7 +8,7 @@ import static seedu.address.testutil.TestUtil.getMidIndex; import static seedu.address.testutil.TestUtil.getPerson; import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON; -import static seedu.address.testutil.TypicalPersons.KEYWORD_MATCHING_MEIER; +import static seedu.address.testutil.typicaladdressbook.TypicalPersons.KEYWORD_MATCHING_MEIER; import org.junit.Test; @@ -73,19 +73,6 @@ public void delete() { command = DeleteCommand.COMMAND_WORD + " " + invalidIndex; assertCommandFailure(command, MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); - /* --------------------- Performing delete operation while a person card is selected ------------------------ */ - - /* Case: delete the selected person -> person list panel selects the person before the deleted person */ - showAllPersons(); - expectedModel = getModel(); - Index selectedIndex = getLastIndex(expectedModel); - Index expectedIndex = Index.fromZeroBased(selectedIndex.getZeroBased() - 1); - selectPerson(selectedIndex); - command = DeleteCommand.COMMAND_WORD + " " + selectedIndex.getOneBased(); - deletedPerson = removePerson(expectedModel, selectedIndex); - expectedResultMessage = String.format(MESSAGE_DELETE_PERSON_SUCCESS, deletedPerson); - assertCommandSuccess(command, expectedModel, expectedResultMessage, expectedIndex); - /* --------------------------------- Performing invalid delete operation ------------------------------------ */ /* Case: invalid index (0) -> rejected */ @@ -112,6 +99,80 @@ public void delete() { assertCommandFailure("DelETE 1", MESSAGE_UNKNOWN_COMMAND); } + @Test + public void deleteAlias() { + /* ----------------- Performing delete operation while an unfiltered list is being shown -------------------- */ + + /* Case: delete the first person in the list, command with leading spaces and trailing spaces -> deleted */ + Model expectedModel = getModel(); + String command = " " + DeleteCommand.COMMAND_ALIAS + " " + + INDEX_FIRST_PERSON.getOneBased() + " "; + Person deletedPerson = removePerson(expectedModel, INDEX_FIRST_PERSON); + String expectedResultMessage = String.format(MESSAGE_DELETE_PERSON_SUCCESS, deletedPerson); + assertCommandSuccess(command, expectedModel, expectedResultMessage); + + /* Case: delete the last person in the list -> deleted */ + Model modelBeforeDeletingLast = getModel(); + Index lastPersonIndex = getLastIndex(modelBeforeDeletingLast); + assertCommandSuccess(lastPersonIndex); + + /* Case: undo deleting the last person in the list -> last person restored */ + command = UndoCommand.COMMAND_ALIAS; + expectedResultMessage = UndoCommand.MESSAGE_SUCCESS; + assertCommandSuccess(command, modelBeforeDeletingLast, expectedResultMessage); + + /* Case: redo deleting the last person in the list -> last person deleted again */ + command = RedoCommand.COMMAND_ALIAS; + removePerson(modelBeforeDeletingLast, lastPersonIndex); + expectedResultMessage = RedoCommand.MESSAGE_SUCCESS; + assertCommandSuccess(command, modelBeforeDeletingLast, expectedResultMessage); + + /* Case: delete the middle person in the list -> deleted */ + Index middlePersonIndex = getMidIndex(getModel()); + assertCommandSuccess(middlePersonIndex); + + /* ------------------ Performing delete operation while a filtered list is being shown ---------------------- */ + + /* Case: filtered person list, delete index within bounds of address book and person list -> deleted */ + showPersonsWithName(KEYWORD_MATCHING_MEIER); + Index index = INDEX_FIRST_PERSON; + assertTrue(index.getZeroBased() < getModel().getFilteredPersonList().size()); + assertCommandSuccess(index); + + /* Case: filtered person list, delete index within bounds of address book but out of bounds of person list + * -> rejected + */ + showPersonsWithName(KEYWORD_MATCHING_MEIER); + int invalidIndex = getModel().getAddressBook().getPersonList().size(); + command = DeleteCommand.COMMAND_ALIAS + " " + invalidIndex; + assertCommandFailure(command, MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + + /* --------------------------------- Performing invalid delete operation ------------------------------------ */ + + /* Case: invalid index (0) -> rejected */ + command = DeleteCommand.COMMAND_ALIAS + " 0"; + assertCommandFailure(command, MESSAGE_INVALID_DELETE_COMMAND_FORMAT); + + /* Case: invalid index (-1) -> rejected */ + command = DeleteCommand.COMMAND_ALIAS + " -1"; + assertCommandFailure(command, MESSAGE_INVALID_DELETE_COMMAND_FORMAT); + + /* Case: invalid index (size + 1) -> rejected */ + Index outOfBoundsIndex = Index.fromOneBased( + getModel().getAddressBook().getPersonList().size() + 1); + command = DeleteCommand.COMMAND_ALIAS + " " + outOfBoundsIndex.getOneBased(); + assertCommandFailure(command, MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + + /* Case: invalid arguments (alphabets) -> rejected */ + assertCommandFailure(DeleteCommand.COMMAND_ALIAS + " abc", MESSAGE_INVALID_DELETE_COMMAND_FORMAT); + + /* Case: invalid arguments (extra argument) -> rejected */ + assertCommandFailure(DeleteCommand.COMMAND_ALIAS + " 1 abc", MESSAGE_INVALID_DELETE_COMMAND_FORMAT); + + /* Case: mixed case command word -> rejected */ + assertCommandFailure("DelETE 1", MESSAGE_UNKNOWN_COMMAND); + } + /** * Removes the {@code Person} at the specified {@code index} in {@code model}'s address book. * @return the removed person @@ -137,7 +198,7 @@ private void assertCommandSuccess(Index toDelete) { String expectedResultMessage = String.format(MESSAGE_DELETE_PERSON_SUCCESS, deletedPerson); assertCommandSuccess( - DeleteCommand.COMMAND_WORD + " " + toDelete.getOneBased(), expectedModel, expectedResultMessage); + DeleteCommand.COMMAND_ALIAS + " " + toDelete.getOneBased(), expectedModel, expectedResultMessage); } /** diff --git a/src/test/java/systemtests/EditCommandSystemTest.java b/src/test/java/systemtests/EditCommandSystemTest.java index 820933203dd9..a8450006922b 100644 --- a/src/test/java/systemtests/EditCommandSystemTest.java +++ b/src/test/java/systemtests/EditCommandSystemTest.java @@ -2,18 +2,14 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; -import static seedu.address.logic.commands.CommandTestUtil.ADDRESS_DESC_AMY; import static seedu.address.logic.commands.CommandTestUtil.ADDRESS_DESC_BOB; -import static seedu.address.logic.commands.CommandTestUtil.EMAIL_DESC_AMY; import static seedu.address.logic.commands.CommandTestUtil.EMAIL_DESC_BOB; import static seedu.address.logic.commands.CommandTestUtil.INVALID_ADDRESS_DESC; import static seedu.address.logic.commands.CommandTestUtil.INVALID_EMAIL_DESC; import static seedu.address.logic.commands.CommandTestUtil.INVALID_NAME_DESC; import static seedu.address.logic.commands.CommandTestUtil.INVALID_PHONE_DESC; import static seedu.address.logic.commands.CommandTestUtil.INVALID_TAG_DESC; -import static seedu.address.logic.commands.CommandTestUtil.NAME_DESC_AMY; import static seedu.address.logic.commands.CommandTestUtil.NAME_DESC_BOB; -import static seedu.address.logic.commands.CommandTestUtil.PHONE_DESC_AMY; import static seedu.address.logic.commands.CommandTestUtil.PHONE_DESC_BOB; import static seedu.address.logic.commands.CommandTestUtil.TAG_DESC_FRIEND; import static seedu.address.logic.commands.CommandTestUtil.TAG_DESC_HUSBAND; @@ -26,9 +22,8 @@ import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS; import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON; -import static seedu.address.testutil.TypicalPersons.AMY; -import static seedu.address.testutil.TypicalPersons.BOB; -import static seedu.address.testutil.TypicalPersons.KEYWORD_MATCHING_MEIER; +import static seedu.address.testutil.typicaladdressbook.TypicalPersons.BOB; +import static seedu.address.testutil.typicaladdressbook.TypicalPersons.KEYWORD_MATCHING_MEIER; import org.junit.Test; @@ -116,20 +111,6 @@ public void edit() throws Exception { assertCommandFailure(EditCommand.COMMAND_WORD + " " + invalidIndex + NAME_DESC_BOB, Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); - /* --------------------- Performing edit operation while a person card is selected -------------------------- */ - - /* Case: selects first card in the person list, edit a person -> edited, card selection remains unchanged but - * browser url changes - */ - showAllPersons(); - index = INDEX_FIRST_PERSON; - selectPerson(index); - command = EditCommand.COMMAND_WORD + " " + index.getOneBased() + NAME_DESC_AMY + PHONE_DESC_AMY + EMAIL_DESC_AMY - + ADDRESS_DESC_AMY + TAG_DESC_FRIEND; - // this can be misleading: card selection actually remains unchanged but the - // browser's url is updated to reflect the new person's name - assertCommandSuccess(command, index, AMY, index); - /* --------------------------------- Performing invalid edit operation -------------------------------------- */ /* Case: invalid index (0) -> rejected */ @@ -188,6 +169,132 @@ public void edit() throws Exception { assertCommandFailure(command, EditCommand.MESSAGE_DUPLICATE_PERSON); } + @Test + public void editAlias() throws Exception { + Model model = getModel(); + + /* ----------------- Performing edit operation while an unfiltered list is being shown ---------------------- */ + + /* Case: edit all fields, command with leading spaces, trailing spaces and multiple spaces between each field + * -> edited + */ + Index index = INDEX_FIRST_PERSON; + String command = " " + EditCommand.COMMAND_ALIAS + " " + index.getOneBased() + " " + NAME_DESC_BOB + " " + + PHONE_DESC_BOB + " " + EMAIL_DESC_BOB + " " + ADDRESS_DESC_BOB + " " + TAG_DESC_HUSBAND + " "; + Person editedPerson = new PersonBuilder().withName(VALID_NAME_BOB).withPhone(VALID_PHONE_BOB) + .withEmail(VALID_EMAIL_BOB).withAddress(VALID_ADDRESS_BOB).withTags(VALID_TAG_HUSBAND).build(); + assertCommandSuccess(command, index, editedPerson); + + /* Case: undo editing the last person in the list -> last person restored */ + command = UndoCommand.COMMAND_ALIAS; + String expectedResultMessage = UndoCommand.MESSAGE_SUCCESS; + assertCommandSuccess(command, model, expectedResultMessage); + + /* Case: redo editing the last person in the list -> last person edited again */ + command = RedoCommand.COMMAND_ALIAS; + expectedResultMessage = RedoCommand.MESSAGE_SUCCESS; + model.updatePerson( + getModel().getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased()), editedPerson); + assertCommandSuccess(command, model, expectedResultMessage); + + /* Case: edit a person with new values same as existing values -> edited */ + command = EditCommand.COMMAND_ALIAS + " " + index.getOneBased() + + NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + + ADDRESS_DESC_BOB + TAG_DESC_FRIEND + TAG_DESC_HUSBAND; + assertCommandSuccess(command, index, BOB); + + /* Case: edit some fields -> edited */ + index = INDEX_FIRST_PERSON; + command = EditCommand.COMMAND_ALIAS + " " + index.getOneBased() + TAG_DESC_FRIEND; + Person personToEdit = getModel().getFilteredPersonList().get(index.getZeroBased()); + editedPerson = new PersonBuilder(personToEdit).withTags(VALID_TAG_FRIEND).build(); + assertCommandSuccess(command, index, editedPerson); + + /* Case: clear tags -> cleared */ + index = INDEX_FIRST_PERSON; + command = EditCommand.COMMAND_ALIAS + " " + index.getOneBased() + " " + PREFIX_TAG.getPrefix(); + editedPerson = new PersonBuilder(personToEdit).withTags().build(); + assertCommandSuccess(command, index, editedPerson); + + /* ------------------ Performing edit operation while a filtered list is being shown ------------------------ */ + + /* Case: filtered person list, edit index within bounds of address book and person list -> edited */ + showPersonsWithName(KEYWORD_MATCHING_MEIER); + index = INDEX_FIRST_PERSON; + assertTrue(index.getZeroBased() < getModel().getFilteredPersonList().size()); + command = EditCommand.COMMAND_ALIAS + " " + index.getOneBased() + " " + NAME_DESC_BOB; + personToEdit = getModel().getFilteredPersonList().get(index.getZeroBased()); + editedPerson = new PersonBuilder(personToEdit).withName(VALID_NAME_BOB).build(); + assertCommandSuccess(command, index, editedPerson); + + /* Case: filtered person list, edit index within bounds of address book but out of bounds of person list + * -> rejected + */ + showPersonsWithName(KEYWORD_MATCHING_MEIER); + int invalidIndex = getModel().getAddressBook().getPersonList().size(); + assertCommandFailure(EditCommand.COMMAND_ALIAS + " " + invalidIndex + NAME_DESC_BOB, + Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + + /* --------------------------------- Performing invalid edit operation -------------------------------------- */ + + /* Case: invalid index (0) -> rejected */ + assertCommandFailure(EditCommand.COMMAND_ALIAS + " 0" + NAME_DESC_BOB, + String.format(Messages.MESSAGE_INVALID_COMMAND_FORMAT, EditCommand.MESSAGE_USAGE)); + + /* Case: invalid index (-1) -> rejected */ + assertCommandFailure(EditCommand.COMMAND_ALIAS + " -1" + NAME_DESC_BOB, + String.format(Messages.MESSAGE_INVALID_COMMAND_FORMAT, EditCommand.MESSAGE_USAGE)); + + /* Case: invalid index (size + 1) -> rejected */ + invalidIndex = getModel().getFilteredPersonList().size() + 1; + assertCommandFailure(EditCommand.COMMAND_ALIAS + " " + invalidIndex + NAME_DESC_BOB, + Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + + /* Case: missing index -> rejected */ + assertCommandFailure(EditCommand.COMMAND_ALIAS + NAME_DESC_BOB, + String.format(Messages.MESSAGE_INVALID_COMMAND_FORMAT, EditCommand.MESSAGE_USAGE)); + + /* Case: missing all fields -> rejected */ + assertCommandFailure(EditCommand.COMMAND_ALIAS + " " + INDEX_FIRST_PERSON.getOneBased(), + EditCommand.MESSAGE_NOT_EDITED); + + /* Case: invalid name -> rejected */ + assertCommandFailure(EditCommand.COMMAND_ALIAS + " " + INDEX_FIRST_PERSON.getOneBased() + INVALID_NAME_DESC, + Name.MESSAGE_NAME_CONSTRAINTS); + + /* Case: invalid phone -> rejected */ + assertCommandFailure(EditCommand.COMMAND_ALIAS + " " + INDEX_FIRST_PERSON.getOneBased() + INVALID_PHONE_DESC, + Phone.MESSAGE_PHONE_CONSTRAINTS); + + /* Case: invalid email -> rejected */ + assertCommandFailure(EditCommand.COMMAND_ALIAS + " " + INDEX_FIRST_PERSON.getOneBased() + INVALID_EMAIL_DESC, + Email.MESSAGE_EMAIL_CONSTRAINTS); + + /* Case: invalid address -> rejected */ + assertCommandFailure(EditCommand.COMMAND_ALIAS + " " + INDEX_FIRST_PERSON.getOneBased() + INVALID_ADDRESS_DESC, + Address.MESSAGE_ADDRESS_CONSTRAINTS); + + /* Case: invalid tag -> rejected */ + assertCommandFailure(EditCommand.COMMAND_ALIAS + " " + INDEX_FIRST_PERSON.getOneBased() + INVALID_TAG_DESC, + Tag.MESSAGE_TAG_CONSTRAINTS); + + /* Case: edit a person with new values same as another person's values -> rejected */ + executeCommand(PersonUtil.getAddCommand(BOB)); + assertTrue(getModel().getAddressBook().getPersonList().contains(BOB)); + index = INDEX_FIRST_PERSON; + assertFalse(getModel().getFilteredPersonList().get(index.getZeroBased()).equals(BOB)); + command = EditCommand.COMMAND_ALIAS + " " + index.getOneBased() + + NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + + ADDRESS_DESC_BOB + TAG_DESC_FRIEND + TAG_DESC_HUSBAND; + assertCommandFailure(command, EditCommand.MESSAGE_DUPLICATE_PERSON); + + /* Case: edit a person with new values same as another person's values but with different tags -> rejected */ + command = EditCommand.COMMAND_ALIAS + " " + index.getOneBased() + + NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + + ADDRESS_DESC_BOB + TAG_DESC_HUSBAND; + assertCommandFailure(command, EditCommand.MESSAGE_DUPLICATE_PERSON); + } + /** * Performs the same verification as {@code assertCommandSuccess(String, Index, Person, Index)} except that * the browser url and selected card remain unchanged. diff --git a/src/test/java/systemtests/FindCommandSystemTest.java b/src/test/java/systemtests/FindCommandSystemTest.java deleted file mode 100644 index 0bde83c0444b..000000000000 --- a/src/test/java/systemtests/FindCommandSystemTest.java +++ /dev/null @@ -1,195 +0,0 @@ -package systemtests; - -import static org.junit.Assert.assertFalse; -import static seedu.address.commons.core.Messages.MESSAGE_PERSONS_LISTED_OVERVIEW; -import static seedu.address.commons.core.Messages.MESSAGE_UNKNOWN_COMMAND; -import static seedu.address.testutil.TypicalPersons.BENSON; -import static seedu.address.testutil.TypicalPersons.CARL; -import static seedu.address.testutil.TypicalPersons.DANIEL; -import static seedu.address.testutil.TypicalPersons.KEYWORD_MATCHING_MEIER; - -import java.util.ArrayList; -import java.util.List; - -import org.junit.Test; - -import seedu.address.commons.core.index.Index; -import seedu.address.logic.commands.DeleteCommand; -import seedu.address.logic.commands.FindCommand; -import seedu.address.logic.commands.RedoCommand; -import seedu.address.logic.commands.UndoCommand; -import seedu.address.model.Model; -import seedu.address.model.tag.Tag; - -public class FindCommandSystemTest extends AddressBookSystemTest { - - @Test - public void find() { - /* Case: find multiple persons in address book, command with leading spaces and trailing spaces - * -> 2 persons found - */ - String command = " " + FindCommand.COMMAND_WORD + " " + KEYWORD_MATCHING_MEIER + " "; - Model expectedModel = getModel(); - ModelHelper.setFilteredList(expectedModel, BENSON, DANIEL); // first names of Benson and Daniel are "Meier" - assertCommandSuccess(command, expectedModel); - assertSelectedCardUnchanged(); - - /* Case: repeat previous find command where person list is displaying the persons we are finding - * -> 2 persons found - */ - command = FindCommand.COMMAND_WORD + " " + KEYWORD_MATCHING_MEIER; - assertCommandSuccess(command, expectedModel); - assertSelectedCardUnchanged(); - - /* Case: find person where person list is not displaying the person we are finding -> 1 person found */ - command = FindCommand.COMMAND_WORD + " Carl"; - ModelHelper.setFilteredList(expectedModel, CARL); - assertCommandSuccess(command, expectedModel); - assertSelectedCardUnchanged(); - - /* Case: find multiple persons in address book, 2 keywords -> 2 persons found */ - command = FindCommand.COMMAND_WORD + " Benson Daniel"; - ModelHelper.setFilteredList(expectedModel, BENSON, DANIEL); - assertCommandSuccess(command, expectedModel); - assertSelectedCardUnchanged(); - - /* Case: find multiple persons in address book, 2 keywords in reversed order -> 2 persons found */ - command = FindCommand.COMMAND_WORD + " Daniel Benson"; - assertCommandSuccess(command, expectedModel); - assertSelectedCardUnchanged(); - - /* Case: find multiple persons in address book, 2 keywords with 1 repeat -> 2 persons found */ - command = FindCommand.COMMAND_WORD + " Daniel Benson Daniel"; - assertCommandSuccess(command, expectedModel); - assertSelectedCardUnchanged(); - - /* Case: find multiple persons in address book, 2 matching keywords and 1 non-matching keyword - * -> 2 persons found - */ - command = FindCommand.COMMAND_WORD + " Daniel Benson NonMatchingKeyWord"; - assertCommandSuccess(command, expectedModel); - assertSelectedCardUnchanged(); - - /* Case: undo previous find command -> rejected */ - command = UndoCommand.COMMAND_WORD; - String expectedResultMessage = UndoCommand.MESSAGE_FAILURE; - assertCommandFailure(command, expectedResultMessage); - - /* Case: redo previous find command -> rejected */ - command = RedoCommand.COMMAND_WORD; - expectedResultMessage = RedoCommand.MESSAGE_FAILURE; - assertCommandFailure(command, expectedResultMessage); - - /* Case: find same persons in address book after deleting 1 of them -> 1 person found */ - executeCommand(DeleteCommand.COMMAND_WORD + " 1"); - assertFalse(getModel().getAddressBook().getPersonList().contains(BENSON)); - command = FindCommand.COMMAND_WORD + " " + KEYWORD_MATCHING_MEIER; - expectedModel = getModel(); - ModelHelper.setFilteredList(expectedModel, DANIEL); - assertCommandSuccess(command, expectedModel); - assertSelectedCardUnchanged(); - - /* Case: find person in address book, keyword is same as name but of different case -> 1 person found */ - command = FindCommand.COMMAND_WORD + " MeIeR"; - assertCommandSuccess(command, expectedModel); - assertSelectedCardUnchanged(); - - /* Case: find person in address book, keyword is substring of name -> 0 persons found */ - command = FindCommand.COMMAND_WORD + " Mei"; - ModelHelper.setFilteredList(expectedModel); - assertCommandSuccess(command, expectedModel); - assertSelectedCardUnchanged(); - - /* Case: find person in address book, name is substring of keyword -> 0 persons found */ - command = FindCommand.COMMAND_WORD + " Meiers"; - ModelHelper.setFilteredList(expectedModel); - assertCommandSuccess(command, expectedModel); - assertSelectedCardUnchanged(); - - /* Case: find person not in address book -> 0 persons found */ - command = FindCommand.COMMAND_WORD + " Mark"; - assertCommandSuccess(command, expectedModel); - assertSelectedCardUnchanged(); - - /* Case: find phone number of person in address book -> 0 persons found */ - command = FindCommand.COMMAND_WORD + " " + DANIEL.getPhone().value; - assertCommandSuccess(command, expectedModel); - assertSelectedCardUnchanged(); - - /* Case: find address of person in address book -> 0 persons found */ - command = FindCommand.COMMAND_WORD + " " + DANIEL.getAddress().value; - assertCommandSuccess(command, expectedModel); - assertSelectedCardUnchanged(); - - /* Case: find email of person in address book -> 0 persons found */ - command = FindCommand.COMMAND_WORD + " " + DANIEL.getEmail().value; - assertCommandSuccess(command, expectedModel); - assertSelectedCardUnchanged(); - - /* Case: find tags of person in address book -> 0 persons found */ - List tags = new ArrayList<>(DANIEL.getTags()); - command = FindCommand.COMMAND_WORD + " " + tags.get(0).tagName; - assertCommandSuccess(command, expectedModel); - assertSelectedCardUnchanged(); - - /* Case: find while a person is selected -> selected card deselected */ - showAllPersons(); - selectPerson(Index.fromOneBased(1)); - assertFalse(getPersonListPanel().getHandleToSelectedCard().getName().equals(DANIEL.getName().fullName)); - command = FindCommand.COMMAND_WORD + " Daniel"; - ModelHelper.setFilteredList(expectedModel, DANIEL); - assertCommandSuccess(command, expectedModel); - assertSelectedCardDeselected(); - - /* Case: find person in empty address book -> 0 persons found */ - deleteAllPersons(); - command = FindCommand.COMMAND_WORD + " " + KEYWORD_MATCHING_MEIER; - expectedModel = getModel(); - ModelHelper.setFilteredList(expectedModel, DANIEL); - assertCommandSuccess(command, expectedModel); - assertSelectedCardUnchanged(); - - /* Case: mixed case command word -> rejected */ - command = "FiNd Meier"; - assertCommandFailure(command, MESSAGE_UNKNOWN_COMMAND); - } - - /** - * Executes {@code command} and verifies that the command box displays an empty string, the result display - * box displays {@code Messages#MESSAGE_PERSONS_LISTED_OVERVIEW} with the number of people in the filtered list, - * and the model related components equal to {@code expectedModel}. - * These verifications are done by - * {@code AddressBookSystemTest#assertApplicationDisplaysExpected(String, String, Model)}.
- * Also verifies that the status bar remains unchanged, and the command box has the default style class, and the - * selected card updated accordingly, depending on {@code cardStatus}. - * @see AddressBookSystemTest#assertApplicationDisplaysExpected(String, String, Model) - */ - private void assertCommandSuccess(String command, Model expectedModel) { - String expectedResultMessage = String.format( - MESSAGE_PERSONS_LISTED_OVERVIEW, expectedModel.getFilteredPersonList().size()); - - executeCommand(command); - assertApplicationDisplaysExpected("", expectedResultMessage, expectedModel); - assertCommandBoxShowsDefaultStyle(); - assertStatusBarUnchanged(); - } - - /** - * Executes {@code command} and verifies that the command box displays {@code command}, the result display - * box displays {@code expectedResultMessage} and the model related components equal to the current model. - * These verifications are done by - * {@code AddressBookSystemTest#assertApplicationDisplaysExpected(String, String, Model)}.
- * Also verifies that the browser url, selected card and status bar remain unchanged, and the command box has the - * error style. - * @see AddressBookSystemTest#assertApplicationDisplaysExpected(String, String, Model) - */ - private void assertCommandFailure(String command, String expectedResultMessage) { - Model expectedModel = getModel(); - - executeCommand(command); - assertApplicationDisplaysExpected(command, expectedResultMessage, expectedModel); - assertSelectedCardUnchanged(); - assertCommandBoxShowsErrorStyle(); - assertStatusBarUnchanged(); - } -} diff --git a/src/test/java/systemtests/FindPersonCommandSystemTest.java b/src/test/java/systemtests/FindPersonCommandSystemTest.java new file mode 100644 index 000000000000..f0c79bd5cee7 --- /dev/null +++ b/src/test/java/systemtests/FindPersonCommandSystemTest.java @@ -0,0 +1,263 @@ +package systemtests; + +import static org.junit.Assert.assertFalse; +import static seedu.address.commons.core.Messages.MESSAGE_PERSONS_LISTED_OVERVIEW; +import static seedu.address.commons.core.Messages.MESSAGE_UNKNOWN_COMMAND; +import static seedu.address.logic.commands.CommandTestUtil.ADDRESS_DESC_AMY; +import static seedu.address.logic.commands.CommandTestUtil.ADDRESS_DESC_BOB; +import static seedu.address.logic.commands.CommandTestUtil.EDUCATION_LEVEL_DESC_AMY; +import static seedu.address.logic.commands.CommandTestUtil.EDUCATION_LEVEL_DESC_BOB; +import static seedu.address.logic.commands.CommandTestUtil.EMAIL_DESC_AMY; +import static seedu.address.logic.commands.CommandTestUtil.EMAIL_DESC_BOB; +import static seedu.address.logic.commands.CommandTestUtil.GRADE_DESC_AMY; +import static seedu.address.logic.commands.CommandTestUtil.GRADE_DESC_BOB; +import static seedu.address.logic.commands.CommandTestUtil.NAME_DESC_AMY; +import static seedu.address.logic.commands.CommandTestUtil.NAME_DESC_BOB; +import static seedu.address.logic.commands.CommandTestUtil.PHONE_DESC_AMY; +import static seedu.address.logic.commands.CommandTestUtil.PHONE_DESC_BOB; +import static seedu.address.logic.commands.CommandTestUtil.SCHOOL_DESC_AMY; +import static seedu.address.logic.commands.CommandTestUtil.SUBJECT_DESC_AMY; +import static seedu.address.logic.commands.CommandTestUtil.TAG_DESC_FRIEND; +import static seedu.address.logic.commands.CommandTestUtil.TAG_DESC_HUSBAND; +import static seedu.address.logic.commands.CommandTestUtil.VALID_ADDRESS_BOB; +import static seedu.address.logic.commands.CommandTestUtil.VALID_EDUCATION_LEVEL_BOB; +import static seedu.address.logic.commands.CommandTestUtil.VALID_EMAIL_BOB; +import static seedu.address.logic.commands.CommandTestUtil.VALID_GRADE_BOB; +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_SCHOOL_AMY; +import static seedu.address.logic.commands.CommandTestUtil.VALID_SUBJECT_AMY; +import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_FRIEND; +import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_HUSBAND; +import static seedu.address.model.person.PersonSortUtil.CATEGORY_EDUCATION_LEVEL; +import static seedu.address.model.person.PersonSortUtil.CATEGORY_GRADE; +import static seedu.address.model.person.PersonSortUtil.CATEGORY_NAME; +import static seedu.address.model.person.PersonSortUtil.CATEGORY_SCHOOL; +import static seedu.address.model.person.PersonSortUtil.CATEGORY_SUBJECT; +import static seedu.address.testutil.typicaladdressbook.TypicalPersons.BENSON; +import static seedu.address.testutil.typicaladdressbook.TypicalPersons.CARL; +import static seedu.address.testutil.typicaladdressbook.TypicalPersons.DANIEL; +import static seedu.address.testutil.typicaladdressbook.TypicalPersons.KEYWORD_MATCHING_MEIER; +import static seedu.address.testutil.typicaladdressbook.TypicalTutees.AMYTUTEE; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.Test; + +import seedu.address.logic.commands.AddTuteeCommand; +import seedu.address.logic.commands.DeleteCommand; +import seedu.address.logic.commands.FindPersonCommand; +import seedu.address.logic.commands.RedoCommand; +import seedu.address.logic.commands.UndoCommand; +import seedu.address.model.Model; +import seedu.address.model.tag.Tag; +import seedu.address.model.tutee.Tutee; +import seedu.address.testutil.TuteeBuilder; + +public class FindPersonCommandSystemTest extends AddressBookSystemTest { + + + @Test + public void find() { + /* Case: find multiple persons in address book, command with leading spaces and trailing spaces + * -> 2 persons found + */ + String command = " " + FindPersonCommand.COMMAND_WORD + " " + CATEGORY_NAME + + " " + KEYWORD_MATCHING_MEIER + " "; + Model expectedModel = getModel(); + ModelHelper.setFilteredList(expectedModel, BENSON, DANIEL); // first names of Benson and Daniel are "Meier" + assertCommandSuccess(command, expectedModel); + assertSelectedCardUnchanged(); + + /* Case: repeat previous find command where person list is displaying the persons we are finding + * -> 2 persons found + */ + command = FindPersonCommand.COMMAND_WORD + " " + CATEGORY_NAME + " " + KEYWORD_MATCHING_MEIER; + assertCommandSuccess(command, expectedModel); + assertSelectedCardUnchanged(); + + /* Case: find person where person list is not displaying the person we are finding -> 1 person found */ + command = FindPersonCommand.COMMAND_WORD + " " + CATEGORY_NAME + " Carl"; + ModelHelper.setFilteredList(expectedModel, CARL); + assertCommandSuccess(command, expectedModel); + assertSelectedCardUnchanged(); + + /* Case: find multiple persons in address book, 2 keywords -> 2 persons found */ + command = FindPersonCommand.COMMAND_WORD + " " + CATEGORY_NAME + " Benson Daniel"; + ModelHelper.setFilteredList(expectedModel, BENSON, DANIEL); + assertCommandSuccess(command, expectedModel); + assertSelectedCardUnchanged(); + + /* Case: find multiple persons in address book, 2 keywords in reversed order -> 2 persons found */ + command = FindPersonCommand.COMMAND_WORD + " " + CATEGORY_NAME + " Daniel Benson"; + assertCommandSuccess(command, expectedModel); + assertSelectedCardUnchanged(); + + /* Case: find multiple persons in address book, 2 keywords with 1 repeat -> 2 persons found */ + command = FindPersonCommand.COMMAND_WORD + " " + CATEGORY_NAME + " Daniel Benson Daniel"; + assertCommandSuccess(command, expectedModel); + assertSelectedCardUnchanged(); + + /* Case: find multiple persons in address book, 2 matching keywords and 1 non-matching keyword + * -> 2 persons found + */ + command = FindPersonCommand.COMMAND_WORD + " " + CATEGORY_NAME + " Daniel Benson NonMatchingKeyWord"; + assertCommandSuccess(command, expectedModel); + assertSelectedCardUnchanged(); + + /* Case: undo previous find command -> rejected */ + command = UndoCommand.COMMAND_WORD; + String expectedResultMessage = UndoCommand.MESSAGE_FAILURE; + assertCommandFailure(command, expectedResultMessage); + + /* Case: redo previous find command -> rejected */ + command = RedoCommand.COMMAND_WORD; + expectedResultMessage = RedoCommand.MESSAGE_FAILURE; + assertCommandFailure(command, expectedResultMessage); + + /* Case: find same persons in address book after deleting 1 of them -> 1 person found */ + executeCommand(DeleteCommand.COMMAND_WORD + " 1"); + assertFalse(getModel().getAddressBook().getPersonList().contains(BENSON)); + command = FindPersonCommand.COMMAND_WORD + " " + CATEGORY_NAME + " " + KEYWORD_MATCHING_MEIER; + expectedModel = getModel(); + ModelHelper.setFilteredList(expectedModel, DANIEL); + assertCommandSuccess(command, expectedModel); + assertSelectedCardUnchanged(); + + /* Case: find person in address book, keyword is same as name but of different case -> 1 person found */ + command = FindPersonCommand.COMMAND_WORD + " " + CATEGORY_NAME + " MeIeR"; + assertCommandSuccess(command, expectedModel); + assertSelectedCardUnchanged(); + + /* Case: find person in address book, keyword is substring of name -> 0 persons found */ + command = FindPersonCommand.COMMAND_WORD + " " + CATEGORY_NAME + " Mei"; + ModelHelper.setFilteredList(expectedModel); + assertCommandSuccess(command, expectedModel); + assertSelectedCardUnchanged(); + + /* Case: find person in address book, name is substring of keyword -> 0 persons found */ + command = FindPersonCommand.COMMAND_WORD + " " + CATEGORY_NAME + " Meiers"; + ModelHelper.setFilteredList(expectedModel); + assertCommandSuccess(command, expectedModel); + assertSelectedCardUnchanged(); + + /* Case: find person not in address book -> 0 persons found */ + command = FindPersonCommand.COMMAND_WORD + " " + CATEGORY_NAME + " Mark"; + assertCommandSuccess(command, expectedModel); + assertSelectedCardUnchanged(); + + /* Case: find address of person in address book -> 0 persons found */ + command = FindPersonCommand.COMMAND_WORD + " " + CATEGORY_NAME + " " + DANIEL.getAddress().value; + assertCommandSuccess(command, expectedModel); + assertSelectedCardUnchanged(); + + /* Case: find email of person in address book -> 0 persons found */ + command = FindPersonCommand.COMMAND_WORD + " " + CATEGORY_NAME + " " + DANIEL.getEmail().value; + assertCommandSuccess(command, expectedModel); + assertSelectedCardUnchanged(); + + /* Case: find tags of person in address book -> 0 persons found */ + List tags = new ArrayList<>(DANIEL.getTags()); + command = FindPersonCommand.COMMAND_WORD + " " + CATEGORY_NAME + " " + tags.get(0).tagName; + assertCommandSuccess(command, expectedModel); + assertSelectedCardUnchanged(); + + //@@author yungyung04 + /* Adding some tutees into the filtered person list to test whether Find Person command can find tutees */ + // adds AMYTUTEE + expectedModel = getModel(); + command = AddTuteeCommand.COMMAND_WORD + " " + NAME_DESC_AMY + " " + PHONE_DESC_AMY + " " + + EMAIL_DESC_AMY + " " + ADDRESS_DESC_AMY + " " + SUBJECT_DESC_AMY + " " + GRADE_DESC_AMY + " " + + EDUCATION_LEVEL_DESC_AMY + " " + SCHOOL_DESC_AMY + " " + TAG_DESC_FRIEND + " "; + addTutee(command, AMYTUTEE, expectedModel); + + //adds Bob whose subject and school are same as Amy's + Tutee modifiedBobTutee = new TuteeBuilder().withName(VALID_NAME_BOB).withPhone(VALID_PHONE_BOB) + .withEmail(VALID_EMAIL_BOB).withAddress(VALID_ADDRESS_BOB).withSubject(VALID_SUBJECT_AMY) + .withGrade(VALID_GRADE_BOB).withEducationLevel(VALID_EDUCATION_LEVEL_BOB).withSchool(VALID_SCHOOL_AMY) + .withTags(VALID_TAG_HUSBAND, VALID_TAG_FRIEND).build(); + command = AddTuteeCommand.COMMAND_WORD + " " + NAME_DESC_BOB + " " + PHONE_DESC_BOB + " " + + EMAIL_DESC_BOB + " " + ADDRESS_DESC_BOB + " " + SUBJECT_DESC_AMY + " " + GRADE_DESC_BOB + " " + + EDUCATION_LEVEL_DESC_BOB + " " + SCHOOL_DESC_AMY + " " + TAG_DESC_HUSBAND + " " + TAG_DESC_FRIEND; + addTutee(command, modifiedBobTutee, expectedModel); + + /* Case: find education level of a tutee in address book -> 1 person found */ + ModelHelper.setFilteredList(expectedModel, AMYTUTEE); + command = FindPersonCommand.COMMAND_WORD + " " + CATEGORY_EDUCATION_LEVEL + " " + + AMYTUTEE.getEducationLevel().toString(); + assertCommandSuccess(command, expectedModel); + assertSelectedCardUnchanged(); + + /* Case: find grade of a tutee using command alias in address book -> 1 person found */ + command = FindPersonCommand.COMMAND_ALIAS + " " + CATEGORY_GRADE + " " + + AMYTUTEE.getGrade().toString(); + assertCommandSuccess(command, expectedModel); + assertSelectedCardUnchanged(); + + /* Case: find school of a tutee in address book -> 2 persons found */ + ModelHelper.setFilteredList(expectedModel, AMYTUTEE, modifiedBobTutee); + command = FindPersonCommand.COMMAND_WORD + " " + CATEGORY_SCHOOL + " " + + AMYTUTEE.getSchool().toString(); + assertCommandSuccess(command, expectedModel); + assertSelectedCardUnchanged(); + + /* Case: find subject of a tutee in address book -> 2 persons found */ + command = FindPersonCommand.COMMAND_WORD + " " + CATEGORY_SUBJECT + " " + + AMYTUTEE.getSubject().toString(); + assertCommandSuccess(command, expectedModel); + assertSelectedCardUnchanged(); + //@@author + + /* Case: find person in empty address book -> 0 persons found */ + deleteAllPersons(); + command = FindPersonCommand.COMMAND_WORD + " " + CATEGORY_NAME + " " + KEYWORD_MATCHING_MEIER; + expectedModel = getModel(); + ModelHelper.setFilteredList(expectedModel, DANIEL); + assertCommandSuccess(command, expectedModel); + assertSelectedCardUnchanged(); + + /* Case: mixed case command word -> rejected */ + command = "FiNd Meier"; + assertCommandFailure(command, MESSAGE_UNKNOWN_COMMAND); + } + + /** + * Executes {@code command} and verifies that the command box displays an empty string, the result display + * box displays {@code Messages#MESSAGE_PERSONS_LISTED_OVERVIEW} with the number of people in the filtered list, + * and the model related components equal to {@code expectedModel}. + * These verifications are done by + * {@code AddressBookSystemTest#assertApplicationDisplaysExpected(String, String, Model)}.
+ * Also verifies that the status bar remains unchanged, and the command box has the default style class, and the + * selected card updated accordingly, depending on {@code cardStatus}. + * @see AddressBookSystemTest#assertApplicationDisplaysExpected(String, String, Model) + */ + private void assertCommandSuccess(String command, Model expectedModel) { + String expectedResultMessage = String.format(FindPersonCommand.MESSAGE_SUCCESS + "\n" + + MESSAGE_PERSONS_LISTED_OVERVIEW, expectedModel.getFilteredPersonList().size()); + + executeCommand(command); + assertApplicationDisplaysExpected("", expectedResultMessage, expectedModel); + assertCommandBoxShowsDefaultStyle(); + assertStatusBarUnchanged(); + } + + /** + * Executes {@code command} and verifies that the command box displays {@code command}, the result display + * box displays {@code expectedResultMessage} and the model related components equal to the current model. + * These verifications are done by + * {@code AddressBookSystemTest#assertApplicationDisplaysExpected(String, String, Model)}.
+ * Also verifies that the browser url, selected card and status bar remain unchanged, and the command box has the + * error style. + * @see AddressBookSystemTest#assertApplicationDisplaysExpected(String, String, Model) + */ + private void assertCommandFailure(String command, String expectedResultMessage) { + Model expectedModel = getModel(); + + executeCommand(command); + assertApplicationDisplaysExpected(command, expectedResultMessage, expectedModel); + assertSelectedCardUnchanged(); + assertCommandBoxShowsErrorStyle(); + assertStatusBarUnchanged(); + } +} diff --git a/src/test/java/systemtests/HelpCommandSystemTest.java b/src/test/java/systemtests/HelpCommandSystemTest.java index 1aa4a5f294f4..fe0031a446e1 100644 --- a/src/test/java/systemtests/HelpCommandSystemTest.java +++ b/src/test/java/systemtests/HelpCommandSystemTest.java @@ -1,11 +1,9 @@ package systemtests; -import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertTrue; import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON; -import static seedu.address.ui.testutil.GuiTestAssert.assertListMatching; import org.junit.Test; @@ -13,8 +11,6 @@ import guitests.guihandles.HelpWindowHandle; import seedu.address.logic.commands.DeleteCommand; import seedu.address.logic.commands.HelpCommand; -import seedu.address.logic.commands.SelectCommand; -import seedu.address.ui.BrowserPanel; import seedu.address.ui.StatusBarFooter; /** @@ -43,10 +39,6 @@ public void openHelpWindow() { getMainMenu().openHelpWindowUsingAccelerator(); assertHelpWindowOpen(); - getBrowserPanel().click(); - getMainMenu().openHelpWindowUsingAccelerator(); - assertHelpWindowNotOpen(); - //use menu button getMainMenu().openHelpWindowUsingMenu(); assertHelpWindowOpen(); @@ -59,14 +51,6 @@ public void openHelpWindow() { executeCommand(HelpCommand.COMMAND_WORD); getMainWindowHandle().focus(); - // assert that while the help window is open the UI updates correctly for a command execution - executeCommand(SelectCommand.COMMAND_WORD + " " + INDEX_FIRST_PERSON.getOneBased()); - assertEquals("", getCommandBox().getInput()); - assertCommandBoxShowsDefaultStyle(); - assertNotEquals(HelpCommand.SHOWING_HELP_MESSAGE, getResultDisplay().getText()); - assertNotEquals(BrowserPanel.DEFAULT_PAGE, getBrowserPanel().getLoadedUrl()); - assertListMatching(getPersonListPanel(), getModel().getFilteredPersonList()); - // assert that the status bar too is updated correctly while the help window is open // note: the select command tested above does not update the status bar executeCommand(DeleteCommand.COMMAND_WORD + " " + INDEX_FIRST_PERSON.getOneBased()); diff --git a/src/test/java/systemtests/ModelHelper.java b/src/test/java/systemtests/ModelHelper.java index b7950598d36b..d9ec67014769 100644 --- a/src/test/java/systemtests/ModelHelper.java +++ b/src/test/java/systemtests/ModelHelper.java @@ -1,12 +1,14 @@ package systemtests; import java.util.Arrays; +import java.util.Comparator; import java.util.List; import java.util.Optional; import java.util.function.Predicate; import seedu.address.model.Model; import seedu.address.model.person.Person; +import seedu.address.model.person.PersonSortUtil; /** * Contains helper methods to set up {@code Model} for testing. @@ -30,6 +32,16 @@ public static void setFilteredList(Model model, Person... toDisplay) { setFilteredList(model, Arrays.asList(toDisplay)); } + //@@author yungyung04 + /** + * Updates {@code model}'s sorted list to display persons based on specified category. + */ + public static void setSortedList(Model model, String category) { + Comparator comparator = new PersonSortUtil().getComparator(category); + model.sortFilteredPersonList(comparator); + } + //@@author + /** * Returns a predicate that evaluates to true if this {@code Person} equals to {@code other}. */ diff --git a/src/test/java/systemtests/SampleDataTest.java b/src/test/java/systemtests/SampleDataTest.java index 81a32ce9487b..23f443e7617e 100644 --- a/src/test/java/systemtests/SampleDataTest.java +++ b/src/test/java/systemtests/SampleDataTest.java @@ -48,4 +48,5 @@ public void addressBook_dataFileDoesNotExist_loadSampleData() { Person[] expectedList = SampleDataUtil.getSamplePersons(); assertListMatching(getPersonListPanel(), expectedList); } + } diff --git a/src/test/java/systemtests/SelectCommandSystemTest.java b/src/test/java/systemtests/SelectCommandSystemTest.java deleted file mode 100644 index c7deb73454b1..000000000000 --- a/src/test/java/systemtests/SelectCommandSystemTest.java +++ /dev/null @@ -1,153 +0,0 @@ -package systemtests; - -import static org.junit.Assert.assertTrue; -import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; -import static seedu.address.commons.core.Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX; -import static seedu.address.commons.core.Messages.MESSAGE_UNKNOWN_COMMAND; -import static seedu.address.logic.commands.SelectCommand.MESSAGE_SELECT_PERSON_SUCCESS; -import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON; -import static seedu.address.testutil.TypicalPersons.KEYWORD_MATCHING_MEIER; -import static seedu.address.testutil.TypicalPersons.getTypicalPersons; - -import org.junit.Test; - -import seedu.address.commons.core.index.Index; -import seedu.address.logic.commands.RedoCommand; -import seedu.address.logic.commands.SelectCommand; -import seedu.address.logic.commands.UndoCommand; -import seedu.address.model.Model; - -public class SelectCommandSystemTest extends AddressBookSystemTest { - @Test - public void select() { - /* ------------------------ Perform select operations on the shown unfiltered list -------------------------- */ - - /* Case: select the first card in the person list, command with leading spaces and trailing spaces - * -> selected - */ - String command = " " + SelectCommand.COMMAND_WORD + " " + INDEX_FIRST_PERSON.getOneBased() + " "; - assertCommandSuccess(command, INDEX_FIRST_PERSON); - - /* Case: select the last card in the person list -> selected */ - Index personCount = Index.fromOneBased(getTypicalPersons().size()); - command = SelectCommand.COMMAND_WORD + " " + personCount.getOneBased(); - assertCommandSuccess(command, personCount); - - /* Case: undo previous selection -> rejected */ - command = UndoCommand.COMMAND_WORD; - String expectedResultMessage = UndoCommand.MESSAGE_FAILURE; - assertCommandFailure(command, expectedResultMessage); - - /* Case: redo selecting last card in the list -> rejected */ - command = RedoCommand.COMMAND_WORD; - expectedResultMessage = RedoCommand.MESSAGE_FAILURE; - assertCommandFailure(command, expectedResultMessage); - - /* Case: select the middle card in the person list -> selected */ - Index middleIndex = Index.fromOneBased(personCount.getOneBased() / 2); - command = SelectCommand.COMMAND_WORD + " " + middleIndex.getOneBased(); - assertCommandSuccess(command, middleIndex); - - /* Case: select the current selected card -> selected */ - assertCommandSuccess(command, middleIndex); - - /* ------------------------ Perform select operations on the shown filtered list ---------------------------- */ - - /* Case: filtered person list, select index within bounds of address book but out of bounds of person list - * -> rejected - */ - showPersonsWithName(KEYWORD_MATCHING_MEIER); - int invalidIndex = getModel().getAddressBook().getPersonList().size(); - assertCommandFailure(SelectCommand.COMMAND_WORD + " " + invalidIndex, MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); - - /* Case: filtered person list, select index within bounds of address book and person list -> selected */ - Index validIndex = Index.fromOneBased(1); - assertTrue(validIndex.getZeroBased() < getModel().getFilteredPersonList().size()); - command = SelectCommand.COMMAND_WORD + " " + validIndex.getOneBased(); - assertCommandSuccess(command, validIndex); - - /* ----------------------------------- Perform invalid select operations ------------------------------------ */ - - /* Case: invalid index (0) -> rejected */ - assertCommandFailure(SelectCommand.COMMAND_WORD + " " + 0, - String.format(MESSAGE_INVALID_COMMAND_FORMAT, SelectCommand.MESSAGE_USAGE)); - - /* Case: invalid index (-1) -> rejected */ - assertCommandFailure(SelectCommand.COMMAND_WORD + " " + -1, - String.format(MESSAGE_INVALID_COMMAND_FORMAT, SelectCommand.MESSAGE_USAGE)); - - /* Case: invalid index (size + 1) -> rejected */ - invalidIndex = getModel().getFilteredPersonList().size() + 1; - assertCommandFailure(SelectCommand.COMMAND_WORD + " " + invalidIndex, MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); - - /* Case: invalid arguments (alphabets) -> rejected */ - assertCommandFailure(SelectCommand.COMMAND_WORD + " abc", - String.format(MESSAGE_INVALID_COMMAND_FORMAT, SelectCommand.MESSAGE_USAGE)); - - /* Case: invalid arguments (extra argument) -> rejected */ - assertCommandFailure(SelectCommand.COMMAND_WORD + " 1 abc", - String.format(MESSAGE_INVALID_COMMAND_FORMAT, SelectCommand.MESSAGE_USAGE)); - - /* Case: mixed case command word -> rejected */ - assertCommandFailure("SeLeCt 1", MESSAGE_UNKNOWN_COMMAND); - - /* Case: select from empty address book -> rejected */ - deleteAllPersons(); - assertCommandFailure(SelectCommand.COMMAND_WORD + " " + INDEX_FIRST_PERSON.getOneBased(), - MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); - } - - /** - * Executes {@code command} and asserts that the,
- * 1. Command box displays an empty string.
- * 2. Command box has the default style class.
- * 3. Result display box displays the success message of executing select command with the - * {@code expectedSelectedCardIndex} of the selected person.
- * 4. {@code Model}, {@code Storage} and {@code PersonListPanel} remain unchanged.
- * 5. Selected card is at {@code expectedSelectedCardIndex} and the browser url is updated accordingly.
- * 6. Status bar remains unchanged.
- * Verifications 1, 3 and 4 are performed by - * {@code AddressBookSystemTest#assertApplicationDisplaysExpected(String, String, Model)}.
- * @see AddressBookSystemTest#assertApplicationDisplaysExpected(String, String, Model) - * @see AddressBookSystemTest#assertSelectedCardChanged(Index) - */ - private void assertCommandSuccess(String command, Index expectedSelectedCardIndex) { - Model expectedModel = getModel(); - String expectedResultMessage = String.format( - MESSAGE_SELECT_PERSON_SUCCESS, expectedSelectedCardIndex.getOneBased()); - int preExecutionSelectedCardIndex = getPersonListPanel().getSelectedCardIndex(); - - executeCommand(command); - assertApplicationDisplaysExpected("", expectedResultMessage, expectedModel); - - if (preExecutionSelectedCardIndex == expectedSelectedCardIndex.getZeroBased()) { - assertSelectedCardUnchanged(); - } else { - assertSelectedCardChanged(expectedSelectedCardIndex); - } - - assertCommandBoxShowsDefaultStyle(); - assertStatusBarUnchanged(); - } - - /** - * Executes {@code command} and asserts that the,
- * 1. Command box displays {@code command}.
- * 2. Command box has the error style class.
- * 3. Result display box displays {@code expectedResultMessage}.
- * 4. {@code Model}, {@code Storage} and {@code PersonListPanel} remain unchanged.
- * 5. Browser url, selected card and status bar remain unchanged.
- * Verifications 1, 3 and 4 are performed by - * {@code AddressBookSystemTest#assertApplicationDisplaysExpected(String, String, Model)}.
- * @see AddressBookSystemTest#assertApplicationDisplaysExpected(String, String, Model) - */ - private void assertCommandFailure(String command, String expectedResultMessage) { - Model expectedModel = getModel(); - - executeCommand(command); - assertApplicationDisplaysExpected(command, expectedResultMessage, expectedModel); - assertSelectedCardUnchanged(); - assertCommandBoxShowsErrorStyle(); - assertStatusBarUnchanged(); - } -}