diff --git a/build.gradle b/build.gradle
index c361f9811e..c64ad741ef 100644
--- a/build.gradle
+++ b/build.gradle
@@ -2,6 +2,7 @@ plugins {
id 'java'
id 'application'
id 'com.github.johnrengelman.shadow' version '7.1.2'
+ id 'checkstyle'
}
repositories {
@@ -11,6 +12,20 @@ repositories {
dependencies {
testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter-api', version: '5.10.0'
testRuntimeOnly group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: '5.10.0'
+ String javaFxVersion = '17.0.7'
+
+ implementation group: 'org.openjfx', name: 'javafx-base', version: javaFxVersion, classifier: 'win'
+ implementation group: 'org.openjfx', name: 'javafx-base', version: javaFxVersion, classifier: 'mac'
+ implementation group: 'org.openjfx', name: 'javafx-base', version: javaFxVersion, classifier: 'linux'
+ implementation group: 'org.openjfx', name: 'javafx-controls', version: javaFxVersion, classifier: 'win'
+ implementation group: 'org.openjfx', name: 'javafx-controls', version: javaFxVersion, classifier: 'mac'
+ implementation group: 'org.openjfx', name: 'javafx-controls', version: javaFxVersion, classifier: 'linux'
+ implementation group: 'org.openjfx', name: 'javafx-fxml', version: javaFxVersion, classifier: 'win'
+ implementation group: 'org.openjfx', name: 'javafx-fxml', version: javaFxVersion, classifier: 'mac'
+ implementation group: 'org.openjfx', name: 'javafx-fxml', version: javaFxVersion, classifier: 'linux'
+ implementation group: 'org.openjfx', name: 'javafx-graphics', version: javaFxVersion, classifier: 'win'
+ implementation group: 'org.openjfx', name: 'javafx-graphics', version: javaFxVersion, classifier: 'mac'
+ implementation group: 'org.openjfx', name: 'javafx-graphics', version: javaFxVersion, classifier: 'linux'
}
test {
@@ -28,14 +43,20 @@ test {
}
application {
- mainClass.set("seedu.duke.Duke")
+ mainClass.set("serenity.gui.Launcher")
}
shadowJar {
- archiveBaseName = "duke"
+ archiveBaseName = "serenity"
archiveClassifier = null
+ archiveFileName = 'serenity.jar'
+}
+
+checkstyle {
+ toolVersion = '10.2'
}
run{
standardInput = System.in
+ enableAssertions = true;
}
diff --git a/config/checkstyle/checkstyle.xml b/config/checkstyle/checkstyle.xml
new file mode 100644
index 0000000000..acac1a8e28
--- /dev/null
+++ b/config/checkstyle/checkstyle.xml
@@ -0,0 +1,434 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/config/checkstyle/suppressions.xml b/config/checkstyle/suppressions.xml
new file mode 100644
index 0000000000..135ea49ee0
--- /dev/null
+++ b/config/checkstyle/suppressions.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/data/serenity.txt b/data/serenity.txt
new file mode 100644
index 0000000000..110d834386
--- /dev/null
+++ b/data/serenity.txt
@@ -0,0 +1,4 @@
+D | 1 | return book | 30 Aug 2024
+T | 1 | read book
+E | 0 | attend meeting | 3pm | 5pm
+D | 0 | write essay | 22 Sep 2024
diff --git a/docs/README.md b/docs/README.md
index 47b9f984f7..b34d6c010c 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -1,30 +1,126 @@
-# Duke User Guide
+# Serenity User Guide
-// Update the title above to match the actual product name
+
-// Product screenshot goes here
+Serenity is a chatbot that helps you to manage your tasks in a simple and efficient manner.
+
+## Adding a todo task
+
+Adds a todo task to the task list.
+
+Format: `todo DESCRIPTION`
+
+Examples:
+- `todo read book`
+- `todo watch movie`
+
+## Adding a deadline task
+
+Adds a deadline task to the task list.
+
+Format: `deadline DESCRIPTION /by DATE`
+
+- The `DATE` should be in the format `DD/MM/YYYY`
+
+Examples:
+- `deadline return book /by 19/09/2024`
+- `deadline write essay /by 20/10/2025`
+
+## Adding an event task
+
+Adds a deadline task to the task list.
+
+Format: `deadline DESCRIPTION /from START /to END`
+
+Examples:
+- `event project meeting /from Monday 2pm /to 4pm`
+- `event hackathon /from Tuesday /to Friday`
+
+## Listing all tasks
+
+Shows a list of all tasks in the task list.
+
+Format: `list`
+
+## Marking a task
+
+Marks the task at the given index as done.
+
+Format: `mark INDEX`
+
+- The index refers to the index number shown in the task list
+- The index must be a positive integer
+- The index cannot be greater than the number of tasks in the task list
+
+Example:
+- `mark 1`
+
+## Unmarking a task
+
+Unmarks the task at the given index as done.
+
+Format: `unmark INDEX`
+
+- The index refers to the index number shown in the task list
+- The index must be a positive integer
+- The index cannot be greater than the number of tasks in the task list
+
+Example:
+- `unmark 1`
+
+## Finding a task
+
+Finds all tasks in the task list which contains the given keyword.
+
+Format: `find KEYWORD`
+
+- The search is case-sensitive e.g. `book` will not match `Book`
+- Only description is searched
+
+Example:
+- `find book`
+- `find write`
+
+## Deleting a task
+Deletes the task at the given index.
+
+Format: `delete INDEX`
+
+- The index refers to te index number shown in the task list
+- The index must be a positive integer
+- The index cannot be greater than the number of tasks in the task list
+
+Example:
+- `delete 1`
+
+## Updating a task
+
+Updates the task at the given index
+
+Format: `update INDEX /update TASK_TYPE DESCRIPTION`
+
+- The task type is either `todo`, `deadline`, or `event`
+- The description required is dependent on the task type
+
+Examples:
+- `update 1 /update todo read mystery book`
+- `update 2 /update deadline return book /by 22/09/2024`
+- `update 3 /update event project meeting /from Monday 3pm /to 5pm`
+
+## Exiting chatbot
+
+Displays goodbye message and exits chatbot.
+
+Format: `bye`
-// Product intro goes here
-## Adding deadlines
-// Describe the action and its outcome.
-// Give examples of usage
-Example: `keyword (optional arguments)`
-// A description of the expected outcome goes here
-```
-expected output
-```
-## Feature ABC
-// Feature details
-## Feature XYZ
-// Feature details
\ No newline at end of file
diff --git a/docs/Ui.png b/docs/Ui.png
new file mode 100644
index 0000000000..298f8604a6
Binary files /dev/null and b/docs/Ui.png differ
diff --git a/src/main/java/Duke.java b/src/main/java/Duke.java
deleted file mode 100644
index 5d313334cc..0000000000
--- a/src/main/java/Duke.java
+++ /dev/null
@@ -1,10 +0,0 @@
-public class Duke {
- public static void main(String[] args) {
- String logo = " ____ _ \n"
- + "| _ \\ _ _| | _____ \n"
- + "| | | | | | | |/ / _ \\\n"
- + "| |_| | |_| | < __/\n"
- + "|____/ \\__,_|_|\\_\\___|\n";
- System.out.println("Hello from\n" + logo);
- }
-}
diff --git a/src/main/java/serenity/Parser.java b/src/main/java/serenity/Parser.java
new file mode 100644
index 0000000000..6d3a810d34
--- /dev/null
+++ b/src/main/java/serenity/Parser.java
@@ -0,0 +1,134 @@
+package serenity;
+
+import java.io.IOException;
+
+import serenity.task.Task;
+import serenity.task.TaskList;
+
+
+/**
+ * Represents a parser that parses user inputs and executes the actions corresponding to the command.
+ */
+public class Parser {
+
+ /**
+ * Parses user input, carries out corresponding actions and
+ * displays corresponding message.
+ *
+ * @param input User's input.
+ * @param tasks List of tasks.
+ * @param ui User interface.
+ * @param storage Data storage.
+ * @throws SerenityException If user input is invalid and task cannot be created.
+ * @throws IOException If there is an issue writing to file that stores data.
+ */
+ public static void parse(String input, TaskList tasks, Ui ui, Storage storage)
+ throws SerenityException, IOException {
+ String[] parts = input.split(" ", 2);
+ String command = parts[0].strip();
+ String message;
+ Task t;
+
+ switch (command) {
+ case "bye":
+ ui.showGoodbye();
+ break;
+ case "list":
+ ui.showTaskList(tasks);
+ break;
+ case "todo", "deadline", "event":
+ t = TaskList.createTask(input);
+ message = tasks.addTask(t);
+ ui.showMessage(message);
+ storage.saveTask(t);
+ break;
+ case "mark", "unmark":
+ message = tasks.changeStatus(input);
+ ui.showMessage(message);
+ storage.writeToFile(tasks);
+ break;
+ case "delete":
+ message = tasks.deleteTask(input);
+ ui.showMessage(message);
+ storage.writeToFile(tasks);
+ break;
+ case "find":
+ message = tasks.findTask(input);
+ ui.showMessage(message);
+ break;
+ case "update":
+ message = tasks.updateTask(input);
+ ui.showMessage(message);
+ storage.writeToFile(tasks);
+ break;
+ default:
+ ui.showMessage("Error: Invalid task.");
+ break;
+ }
+
+ }
+
+ /**
+ * Parses user input, carries out corresponding actions and
+ * returns message to be displayed.
+ *
+ * @param input User's input
+ * @param tasks List of tasks.
+ * @param storage Data storage
+ * @return Message to be displayed.
+ */
+ public static String parseToString(String input, TaskList tasks, Storage storage) {
+ String[] parts = input.split(" ", 2);
+ String command = parts[0].strip();
+ String message;
+ Task t;
+
+ try {
+ switch (command) {
+ case "bye":
+ message = "Goodbye. Hope to see you again soon!";
+ break;
+ case "list":
+ message = tasks.toString();
+ break;
+ case "todo", "deadline", "event":
+ t = TaskList.createTask(input);
+ message = tasks.addTask(t);
+ storage.saveTask(t);
+ break;
+ case "mark", "unmark":
+ message = tasks.changeStatus(input);
+ storage.writeToFile(tasks);
+ break;
+ case "delete":
+ message = tasks.deleteTask(input);
+ storage.writeToFile(tasks);
+ break;
+ case "find":
+ message = tasks.findTask(input);
+ break;
+ case "update":
+ message = tasks.updateTask(input);
+ storage.writeToFile(tasks);
+ break;
+ default:
+ message = "Error: Invalid task.";
+ break;
+ }
+ } catch (SerenityException | IOException e) {
+ return e.getMessage();
+ }
+ return message;
+ }
+
+ /**
+ * Checks if command is to exit chatbot.
+ *
+ * @param input User's input.
+ * @return True if the command is bye.
+ */
+ public static boolean isExit(String input) {
+ return input.startsWith("bye");
+ }
+
+}
diff --git a/src/main/java/serenity/Serenity.java b/src/main/java/serenity/Serenity.java
new file mode 100644
index 0000000000..af97c83872
--- /dev/null
+++ b/src/main/java/serenity/Serenity.java
@@ -0,0 +1,93 @@
+package serenity;
+
+import java.io.IOException;
+
+import serenity.task.TaskList;
+
+/**
+ * Represents a chatbot used for task management.
+ */
+@SuppressWarnings("checkstyle:Regexp")
+public class Serenity {
+
+ private static final String DEFAULT_FILE_PATH = "./data/serenity.txt";
+ private Storage storage;
+ private TaskList tasks;
+ private Ui ui;
+
+ /**
+ * Constructs a Serenity object.
+ *
+ * @param filePath The path of file where data is stored.
+ */
+ public Serenity(String filePath) {
+ ui = new Ui();
+ storage = new Storage(filePath);
+ try {
+ tasks = new TaskList(storage.loadFile());
+ } catch (SerenityException e) {
+ ui.showLoadingError();
+ tasks = new TaskList();
+ } catch (IOException e) {
+ ui.showMessage(e.getMessage());
+ }
+ }
+
+ /**
+ * Constructs a Serenity object with default file path.
+ */
+ public Serenity() {
+ ui = new Ui();
+ storage = new Storage(DEFAULT_FILE_PATH);
+ try {
+ tasks = new TaskList(storage.loadFile());
+ } catch (SerenityException e) {
+ ui.showLoadingError();
+ tasks = new TaskList();
+ } catch (IOException e) {
+ ui.showMessage(e.getMessage());
+ }
+ }
+
+ /**
+ * Generates response to user's input in chat.
+ *
+ * @param input User's input
+ */
+ public String getResponse(String input) {
+ return Parser.parseToString(input, tasks, storage);
+ }
+
+ /**
+ * Generates welcome message.
+ *
+ * @return Welcome message.
+ */
+ public String welcome() {
+ return "Hi, I'm Serenity!\n" + "What can I do for you?";
+ }
+
+
+ /**
+ * Runs the Serenity chatbot
+ */
+ public void run() {
+
+ ui.showWelcome();
+ boolean isExit = false;
+
+ while (!isExit) {
+ try {
+ String input = ui.readCommand();
+ Parser.parse(input, tasks, ui, storage);
+ isExit = Parser.isExit(input);
+ } catch (SerenityException | IOException e) {
+ ui.showMessage(e.getMessage());
+ }
+ }
+ }
+ public static void main(String[] args) {
+ new Serenity("./data/serenity.txt").run();
+ }
+
+}
diff --git a/src/main/java/serenity/SerenityException.java b/src/main/java/serenity/SerenityException.java
new file mode 100644
index 0000000000..b844c01725
--- /dev/null
+++ b/src/main/java/serenity/SerenityException.java
@@ -0,0 +1,16 @@
+package serenity;
+
+/**
+ * Represents a custom exception in the Serenity class.
+ */
+public class SerenityException extends Exception {
+
+ /**
+ * Constructs a SerenityException.
+ *
+ * @param msg The error message of the exception.
+ */
+ public SerenityException(String msg) {
+ super(msg);
+ }
+}
diff --git a/src/main/java/serenity/Storage.java b/src/main/java/serenity/Storage.java
new file mode 100644
index 0000000000..5402159a5b
--- /dev/null
+++ b/src/main/java/serenity/Storage.java
@@ -0,0 +1,139 @@
+package serenity;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.time.LocalDate;
+import java.time.format.DateTimeFormatter;
+import java.util.ArrayList;
+import java.util.Scanner;
+
+import serenity.task.Deadline;
+import serenity.task.Event;
+import serenity.task.Task;
+import serenity.task.TaskList;
+import serenity.task.Todo;
+
+
+/**
+ * Handles the storing, loading and saving of tasks to data file.
+ */
+public class Storage {
+
+ protected String filePath;
+
+ /**
+ * Constructs a Storage object.
+ *
+ * @param filePath The path of file where data is stored.
+ */
+ public Storage(String filePath) {
+ this.filePath = filePath;
+ }
+
+ /**
+ * Loads tasks stored in data file to chatbot.
+ *
+ * @return ArrayList of tasks loaded from data file.
+ * @throws IOException If there are issues with creating directory or file
+ * @throws SerenityException If there are issues with loading a task.
+ */
+ public ArrayList loadFile() throws IOException, SerenityException {
+ File f = new File(filePath);
+ File directory = new File(f.getParentFile().getAbsolutePath());
+ directory.mkdirs();
+ if (!f.exists()) {
+ Files.createFile(Paths.get(filePath));
+ }
+
+ assert f.exists() : "File should exist.";
+
+ Scanner sc = new Scanner(f);
+ ArrayList tasks = new ArrayList<>();
+ while (sc.hasNext()) {
+ tasks.add(loadTask(sc.nextLine()));
+ }
+ sc.close();
+ return tasks;
+ }
+
+ /**
+ * Converts string representation of task stored in data file to a Task.
+ *
+ * @param taskDescription String representation of task stored in data file.
+ * @return Task created with the task description.
+ * @throws SerenityException If task description is invalid and task cannot be created.
+ */
+ public Task loadTask(String taskDescription) throws SerenityException {
+ String[] parts = taskDescription.split("\\|");
+ Task t;
+
+ for (int i = 0; i < parts.length; i++) {
+ parts[i] = parts[i].strip();
+ }
+
+ String taskType = parts[0];
+
+ switch (taskType) {
+ case "T":
+ t = new Todo(parts[2]);
+ break;
+ case "D":
+ DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd MMM yyyy");
+ LocalDate date = LocalDate.parse(parts[3], formatter);
+ String by = date.format(DateTimeFormatter.ofPattern("dd/MM/yyyy"));
+ t = new Deadline(parts[2], by);
+ break;
+ case "E":
+ t = new Event(parts[2], parts[3], parts[4]);
+ break;
+ default:
+ throw new SerenityException("Error: Task cannot be loaded.");
+ }
+
+ if (parts[1].equals("1")) {
+ t.markAsDone();
+ }
+
+ return t;
+ }
+
+ /**
+ * Writes to data file by appending text to existing content.
+ *
+ * @param textToAppend Text to be appended.
+ * @throws IOException If there are issues accessing file.
+ */
+ public void appendToFile(String textToAppend) throws IOException {
+ FileWriter fw = new FileWriter(filePath, true);
+ fw.write(textToAppend + "\n");
+ fw.close();
+ }
+
+ /**
+ * Saves task to data file.
+ *
+ * @param t Task to be saved to file.
+ * @throws IOException If there are issues appending text to file.
+ */
+ public void saveTask(Task t) throws IOException {
+ String textToAppend = t.formatData();
+ appendToFile(textToAppend);
+ }
+
+ /**
+ * Writes to file by overwriting existing content.
+ *
+ * @param tasks Tasks to be saved to file.
+ * @throws IOException If there are issues writing to file.
+ */
+ public void writeToFile(TaskList tasks) throws IOException {
+ //overwrite
+ FileWriter fw = new FileWriter(filePath);
+ fw.write(tasks.formatData());
+ fw.close();
+ }
+
+}
diff --git a/src/main/java/serenity/Ui.java b/src/main/java/serenity/Ui.java
new file mode 100644
index 0000000000..41b90504c1
--- /dev/null
+++ b/src/main/java/serenity/Ui.java
@@ -0,0 +1,78 @@
+package serenity;
+
+import java.util.Scanner;
+
+import serenity.task.TaskList;
+
+
+/**
+ * Represents user interface that handles user interactions.
+ */
+public class Ui {
+
+ private static final String HORIZONTAL_LINE = "__________________________________________";
+ protected Scanner sc;
+
+ /**
+ * Constructs Ui object
+ */
+ public Ui() {
+ this.sc = new Scanner(System.in);
+ }
+
+ /**
+ * Displays welcome message.
+ */
+ public void showWelcome() {
+ System.out.println(HORIZONTAL_LINE);
+ System.out.println("Hi, I'm Serenity!\n" + "What can I do for you?");
+ System.out.println(HORIZONTAL_LINE);
+ }
+
+ /**
+ * Displays goodbye message and close scanner.
+ */
+ public void showGoodbye() {
+ this.sc.close();
+ System.out.println(HORIZONTAL_LINE);
+ System.out.println("Goodbye. Hope to see you again soon!");
+ System.out.println(HORIZONTAL_LINE);
+ }
+
+ /**
+ * Gets command from user input.
+ * @return User's input.
+ */
+ public String readCommand() {
+ return this.sc.nextLine();
+ }
+
+ /**
+ * Displays message of loading error.
+ */
+ public void showLoadingError() {
+ System.out.println("Error: File cannot be loaded.");
+ }
+
+ /**
+ * Displays message to user.
+ *
+ * @param message Message to be displayed.
+ */
+ public void showMessage(String message) {
+ System.out.println(HORIZONTAL_LINE);
+ System.out.println(message);
+ System.out.println(HORIZONTAL_LINE);
+ }
+
+ /**
+ * Displays TaskList to user.
+ *
+ * @param tasks TaskList to be displayed.
+ */
+ public void showTaskList(TaskList tasks) {
+ System.out.println(HORIZONTAL_LINE);
+ System.out.println(tasks.toString());
+ System.out.println(HORIZONTAL_LINE);
+ }
+}
diff --git a/src/main/java/serenity/gui/DialogBox.java b/src/main/java/serenity/gui/DialogBox.java
new file mode 100644
index 0000000000..94a34ccf7c
--- /dev/null
+++ b/src/main/java/serenity/gui/DialogBox.java
@@ -0,0 +1,61 @@
+package serenity.gui;
+
+import java.io.IOException;
+import java.util.Collections;
+
+import javafx.collections.FXCollections;
+import javafx.collections.ObservableList;
+import javafx.fxml.FXML;
+import javafx.fxml.FXMLLoader;
+import javafx.geometry.Pos;
+import javafx.scene.Node;
+import javafx.scene.control.Label;
+import javafx.scene.image.Image;
+import javafx.scene.image.ImageView;
+import javafx.scene.layout.HBox;
+
+/**
+ * Represents a dialog box consisting of an ImageView to represent the speaker's face
+ * and a label containing text from the speaker.
+ */
+public class DialogBox extends HBox {
+ @FXML
+ private Label dialog;
+ @FXML
+ private ImageView displayPicture;
+
+ private DialogBox(String text, Image img) {
+ try {
+ FXMLLoader fxmlLoader = new FXMLLoader(MainWindow.class.getResource("/view/DialogBox.fxml"));
+ fxmlLoader.setController(this);
+ fxmlLoader.setRoot(this);
+ fxmlLoader.load();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+
+ dialog.setText(text);
+ displayPicture.setImage(img);
+ }
+
+ /**
+ * Flips the dialog box such that the ImageView is on the left and text on the right.
+ */
+ private void flip() {
+ ObservableList tmp = FXCollections.observableArrayList(this.getChildren());
+ Collections.reverse(tmp);
+ getChildren().setAll(tmp);
+ setAlignment(Pos.TOP_LEFT);
+ }
+
+ public static DialogBox getUserDialog(String text, Image img) {
+ return new DialogBox(text, img);
+ }
+
+ public static DialogBox getSerenityDialog(String text, Image img) {
+ var db = new DialogBox(text, img);
+ db.flip();
+ return db;
+ }
+}
+
diff --git a/src/main/java/serenity/gui/Launcher.java b/src/main/java/serenity/gui/Launcher.java
new file mode 100644
index 0000000000..1ec4d42717
--- /dev/null
+++ b/src/main/java/serenity/gui/Launcher.java
@@ -0,0 +1,12 @@
+package serenity.gui;
+
+import javafx.application.Application;
+
+/**
+ * A launcher class to workaround classpath issues.
+ */
+public class Launcher {
+ public static void main(String[] args) {
+ Application.launch(Main.class, args);
+ }
+}
diff --git a/src/main/java/serenity/gui/Main.java b/src/main/java/serenity/gui/Main.java
new file mode 100644
index 0000000000..e9db461ae2
--- /dev/null
+++ b/src/main/java/serenity/gui/Main.java
@@ -0,0 +1,37 @@
+package serenity.gui;
+
+import java.io.IOException;
+
+import javafx.application.Application;
+import javafx.fxml.FXMLLoader;
+import javafx.scene.Scene;
+import javafx.scene.layout.AnchorPane;
+import javafx.stage.Stage;
+import serenity.Serenity;
+
+/**
+ * A GUI for Duke using FXML.
+ */
+public class Main extends Application {
+
+ private Serenity serenity = new Serenity();
+
+ @Override
+ public void start(Stage stage) {
+ try {
+ FXMLLoader fxmlLoader = new FXMLLoader(Main.class.getResource("/view/MainWindow.fxml"));
+ AnchorPane ap = fxmlLoader.load();
+ Scene scene = new Scene(ap);
+ stage.setScene(scene);
+ stage.setTitle("Serenity");
+ // inject the serenity instance
+ MainWindow window = fxmlLoader.getController();
+ window.setSerenity(serenity);
+ window.start();
+ stage.show();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+}
+
diff --git a/src/main/java/serenity/gui/MainWindow.java b/src/main/java/serenity/gui/MainWindow.java
new file mode 100644
index 0000000000..3f2f0d49e8
--- /dev/null
+++ b/src/main/java/serenity/gui/MainWindow.java
@@ -0,0 +1,66 @@
+package serenity.gui;
+
+import javafx.application.Platform;
+import javafx.fxml.FXML;
+import javafx.scene.control.Button;
+import javafx.scene.control.ScrollPane;
+import javafx.scene.control.TextField;
+import javafx.scene.image.Image;
+import javafx.scene.layout.AnchorPane;
+import javafx.scene.layout.VBox;
+import serenity.Serenity;
+
+/**
+ * Controller for the main GUI.
+ */
+public class MainWindow extends AnchorPane {
+ @FXML
+ private ScrollPane scrollPane;
+ @FXML
+ private VBox dialogContainer;
+ @FXML
+ private TextField userInput;
+ @FXML
+ private Button sendButton;
+
+ private Serenity serenity;
+
+ private Image userImage = new Image(this.getClass().getResourceAsStream("/images/User.png"));
+ private Image serenityImage = new Image(this.getClass().getResourceAsStream("/images/Serenity.png"));
+
+ @FXML
+ public void initialize() {
+ scrollPane.vvalueProperty().bind(dialogContainer.heightProperty());
+ }
+
+ /** Injects the Serenity instance */
+ public void setSerenity(Serenity s) {
+ serenity = s;
+ }
+
+ /**
+ * Starts chatbot with welcome message.
+ */
+
+ public void start() {
+ dialogContainer.getChildren().addAll(DialogBox.getSerenityDialog(serenity.welcome(), serenityImage));
+ }
+
+ /**
+ * Creates two dialog boxes, one echoing user input and the other containing Duke's reply and then appends them to
+ * the dialog container. Clears the user input after processing.
+ */
+ @FXML
+ private void handleUserInput() {
+ String input = userInput.getText();
+ String response = serenity.getResponse(input);
+ dialogContainer.getChildren().addAll(
+ DialogBox.getUserDialog(input, userImage),
+ DialogBox.getSerenityDialog(response, serenityImage)
+ );
+ userInput.clear();
+ if (input.equals("bye")) {
+ Platform.exit();
+ }
+ }
+}
diff --git a/src/main/java/serenity/task/Deadline.java b/src/main/java/serenity/task/Deadline.java
new file mode 100644
index 0000000000..88c62b5a2d
--- /dev/null
+++ b/src/main/java/serenity/task/Deadline.java
@@ -0,0 +1,55 @@
+package serenity.task;
+
+import java.time.LocalDate;
+import java.time.format.DateTimeFormatter;
+import java.time.format.DateTimeParseException;
+
+import serenity.SerenityException;
+
+
+
+/**
+ * Represents a type of Task that has a deadline.
+ */
+public class Deadline extends Task {
+
+ protected LocalDate date;
+
+ /**
+ * Constructs a Deadline.
+ *
+ * @param description Description of task.
+ * @param by Deadline of task.
+ */
+ public Deadline(String description, String by) throws SerenityException {
+ super(description);
+ try {
+ DateTimeFormatter formatter = DateTimeFormatter.ofPattern("[dd/MM/yyyy]");
+ this.date = LocalDate.parse(by, formatter);
+ } catch (DateTimeParseException e) {
+ throw new SerenityException("Error: Invalid date. Please enter date in format dd/mm/yyyy.");
+ }
+ }
+
+ /**
+ * Returns string representation of Deadline.
+ *
+ * @return String representation.
+ */
+ @Override
+ public String toString() {
+ String formatDate = date.format(DateTimeFormatter.ofPattern("dd MMM yyyy"));
+ return "[D]" + super.toString() + " (by: " + formatDate + ")";
+ }
+
+ /**
+ * Returns string representation of Deadline to save as data.
+ *
+ * @return String representation to save as data.
+ */
+ @Override
+ public String formatData() {
+ String formatDate = date.format(DateTimeFormatter.ofPattern("dd MMM yyyy"));
+ return "D | " + super.formatData() + " | " + formatDate;
+ }
+}
diff --git a/src/main/java/serenity/task/Event.java b/src/main/java/serenity/task/Event.java
new file mode 100644
index 0000000000..78a0678d57
--- /dev/null
+++ b/src/main/java/serenity/task/Event.java
@@ -0,0 +1,44 @@
+package serenity.task;
+
+/**
+ * Represents a type of Task that has start and end timings.
+ */
+public class Event extends Task {
+
+ protected String start;
+ protected String end;
+
+
+ /**
+ * Constructs an Event.
+ *
+ * @param description Description of task.
+ * @param start Start time of task.
+ * @param end End time of task.
+ */
+ public Event(String description, String start, String end) {
+ super(description);
+ this.start = start;
+ this.end = end;
+ }
+
+ /**
+ * Returns string representation of Event.
+ *
+ * @return String representation.
+ */
+ @Override
+ public String toString() {
+ return "[E]" + super.toString() + " (from: " + start + " to: " + end + ")";
+ }
+
+ /**
+ * Returns string representation of Event to save as data.
+ *
+ * @return String representation to save as data.
+ */
+ @Override
+ public String formatData() {
+ return "E | " + super.formatData() + " | " + this.start + " | " + this.end;
+ }
+}
diff --git a/src/main/java/serenity/task/Task.java b/src/main/java/serenity/task/Task.java
new file mode 100644
index 0000000000..c89f39a3cd
--- /dev/null
+++ b/src/main/java/serenity/task/Task.java
@@ -0,0 +1,64 @@
+package serenity.task;
+
+/**
+ * Represents a general task with a description that can be marked done or undone.
+ */
+public class Task {
+ protected String description;
+ protected boolean isDone;
+
+ /**
+ * Constructs a Task.
+ *
+ * @param description Description of task.
+ */
+ public Task(String description) {
+ this.description = description;
+ this.isDone = false;
+ }
+
+
+ /**
+ * Returns string representation of status of task.
+ *
+ * @return String representation of status of task.
+ */
+ public String getStatusIcon() {
+ return (isDone ? "X" : " "); // mark done task with X
+ }
+
+ /**
+ * Marks task as done.
+ */
+ public void markAsDone() {
+ this.isDone = true;
+ }
+
+ /**
+ * Marks task as undone.
+ */
+ public void markAsNotDone() {
+ this.isDone = false;
+ }
+
+ /**
+ * Returns string representation of a Task.
+ *
+ * @return String representation.
+ */
+ @Override
+ public String toString() {
+ return "[" + this.getStatusIcon() + "] " + this.description;
+ }
+
+ /**
+ * Returns string representation of Task to save as data.
+ *
+ * @return String representation to save as data.
+ */
+ public String formatData() {
+ int i = this.isDone ? 1 : 0;
+ return i + " | " + this.description;
+ }
+
+}
diff --git a/src/main/java/serenity/task/TaskList.java b/src/main/java/serenity/task/TaskList.java
new file mode 100644
index 0000000000..e7e214aca0
--- /dev/null
+++ b/src/main/java/serenity/task/TaskList.java
@@ -0,0 +1,286 @@
+package serenity.task;
+
+import java.util.ArrayList;
+
+import serenity.SerenityException;
+
+/**
+ * Represents a list of tasks, where tasks can be added, deleted
+ * and marked as done or undone.
+ */
+public class TaskList {
+
+ protected ArrayList tasks;
+
+ /**
+ * Constructs a TaskList with no existing tasks.
+ */
+ public TaskList() {
+ this.tasks = new ArrayList<>();
+ }
+
+ /**
+ * Constructs a TaskList with existing tasks.
+ *
+ * @param tasks ArrayList of existing tasks.
+ */
+ public TaskList(ArrayList tasks) {
+ this.tasks = tasks;
+ }
+
+ /**
+ * Creates task of specified type with given task description.
+ *
+ * @param input Task description specifying type of task.
+ * @return Task of specified type with given task description.
+ * @throws SerenityException If type of task is not specified.
+ */
+ public static Task createTask(String input) throws SerenityException {
+ Task t;
+
+ if (input.startsWith("todo")) {
+ t = createTodo(input);
+ } else if (input.startsWith("deadline")) {
+ t = createDeadline(input);
+ } else if (input.startsWith("event")) {
+ t = createEvent(input);
+ } else {
+ throw new SerenityException("Error: Type of task is invalid.");
+ }
+
+ return t;
+ }
+
+ /**
+ * Creates Event with given task description.
+ *
+ * @param input Task description.
+ * @return Event with given task description.
+ * @throws SerenityException If task description is missing.
+ */
+ private static Task createEvent(String input) throws SerenityException {
+ String[] components = input.split(" ");
+ if (components.length == 1) {
+ throw new SerenityException("Error: The description of an event cannot be empty.");
+ }
+ String taskDescription = input.split(" ", 2)[1];
+ if (!taskDescription.contains("/from") || !taskDescription.contains("/to")) {
+ throw new SerenityException("Error: Missing event info.");
+ }
+ String[] parts = taskDescription.split("/from");
+ if (parts.length == 1) {
+ throw new SerenityException("Error: Missing event info.");
+ }
+ String[] timings = parts[1].split("/to");
+ if (timings.length == 1 || parts[0].isBlank() || timings[0].isBlank()
+ || timings[1].isBlank()) {
+ throw new SerenityException("Error: Missing event info.");
+ }
+ return new Event(parts[0].strip(), timings[0].strip(), timings[1].strip());
+ }
+
+ /**
+ * Creates Deadline with given task description.
+ *
+ * @param input Task description.
+ * @return Deadline with given task description.
+ * @throws SerenityException If task description is missing.
+ */
+ private static Task createDeadline(String input) throws SerenityException {
+ String[] components = input.split(" ");
+ if (components.length == 1) {
+ throw new SerenityException("Error: The description of a deadline cannot be empty.");
+ }
+ String taskDescription = input.split(" ", 2)[1];
+ if (!taskDescription.contains("/by")) {
+ throw new SerenityException("Error: Missing deadline info.");
+ }
+ String[] parts = taskDescription.split("/by");
+ if (parts[0].isBlank() || parts[1].isBlank()) {
+ throw new SerenityException("Error: Missing deadline info.");
+ }
+ return new Deadline(parts[0].strip(), parts[1].strip());
+ }
+
+ /**
+ * Creates Todo with given task description.
+ *
+ * @param input Task description.
+ * @return Deadline with given task description.
+ * @throws SerenityException If task description is missing.
+ */
+ private static Todo createTodo(String input) throws SerenityException {
+ String[] components = input.split(" ");
+ if (components.length == 1) {
+ throw new SerenityException("Error: The description of a todo cannot be empty.");
+ }
+ String taskDescription = input.split(" ", 2)[1];
+ return new Todo(taskDescription.strip());
+ }
+
+ /**
+ * Adds task to task list.
+ *
+ * @param t Task to be added.
+ * @return Message to be displayed when task is added.
+ */
+ public String addTask(Task t) {
+ tasks.add(t);
+ assert tasks.contains(t) : "Task failed to be added to task list";
+
+ String numOfTasks = tasks.size() == 1 ? "task" : "tasks";
+ return ("Got it. I've added this task:\n" + t
+ + "\nNow you have " + tasks.size() + " " + numOfTasks + " in the list.");
+
+ }
+
+ /**
+ * Deletes task from task list.
+ *
+ * @param input Command containing index of task to delete.
+ * @return Message to be displayed when task is deleted.
+ * @throws SerenityException If task index is not specified.
+ */
+ public String deleteTask(String input) throws SerenityException {
+
+ String[] parts = input.split(" ");
+ if (parts.length == 1) {
+ throw new SerenityException("Error: Missing index.");
+ }
+
+ int index = Integer.parseInt(input.substring(7)) - 1;
+ if (index < 0 || index > tasks.size() - 1) {
+ throw new SerenityException("Error: Index is out of bounds.");
+ }
+ Task t = tasks.get(index);
+ tasks.remove(index);
+
+ String numOfTasks = tasks.size() == 1 ? "task" : "tasks";
+ return ("Noted. I've removed this task:\n" + t
+ + "\nNow you have " + tasks.size() + " " + numOfTasks + " in the list.");
+ }
+
+ /**
+ * Changes status of task.
+ *
+ * @param input Command to containing index of task to change status.
+ * @return Message to be displayed to user when task status is changed.
+ * @throws SerenityException If task index is not specified.
+ */
+ public String changeStatus(String input) throws SerenityException {
+
+ String[] parts = input.split(" ");
+ if (parts.length == 1) {
+ throw new SerenityException("Error: Missing index.");
+ }
+
+ String message;
+
+ if (input.startsWith("mark")) {
+ int index = Integer.parseInt(input.substring(5)) - 1;
+ if (index < 0 || index > tasks.size() - 1) {
+ throw new SerenityException("Error: Index is out of bounds.");
+ }
+ Task t = tasks.get(index);
+ t.markAsDone();
+ message = "Nice! I've marked this task as done:\n" + t;
+ } else if (input.startsWith("unmark")) {
+ int index = Integer.parseInt(input.substring(7)) - 1;
+ if (index < 0 || index > tasks.size() - 1) {
+ throw new SerenityException("Error: Index is out of bounds.");
+ }
+ Task t = tasks.get(index);
+ t.markAsNotDone();
+ message = "OK, I've marked this task as not done yet:\n" + t;
+ } else {
+ throw new SerenityException("Error: Type of task is not specified.");
+ }
+ return message;
+ }
+
+ /**
+ * Returns String representation of TaskList of matching tasks.
+ * Matching tasks are identified by the given keyword.
+ *
+ * @param input keyword to search tasks for
+ * @return String representation of TaskList of matching tasks
+ * @throws SerenityException If keyword is missing.
+ */
+ public String findTask(String input) throws SerenityException {
+
+ String[] parts = input.split(" ");
+ if (parts.length == 1) {
+ throw new SerenityException("Error: Missing keyword.");
+ }
+
+ String keyWord = parts[1].strip();
+ ArrayList foundTasks = new ArrayList<>();
+ for (int i = 0; i < tasks.size(); i++) {
+ if (tasks.get(i).toString().contains(keyWord)) {
+ foundTasks.add(tasks.get(i));
+ }
+ }
+
+ String message = "Here are the matching tasks in your list:";
+ for (int j = 0; j < foundTasks.size(); j++) {
+ int index = j + 1;
+ message += "\n" + index + ". " + foundTasks.get(j);
+ }
+ return message;
+ }
+
+ /**
+ * Updates task at specified index.
+ *
+ * @param input User's input.
+ * @return Message to be displayed to user when task is updated.
+ * @throws SerenityException If update info or task index is missing.
+ */
+ public String updateTask(String input) throws SerenityException {
+ String[] parts = input.split("/update");
+ if (parts.length == 1) {
+ throw new SerenityException("Error: Missing info to update task with.");
+ }
+ String[] command = parts[0].split(" ");
+ if (command.length == 1) {
+ throw new SerenityException("Error: Missing index.");
+ }
+ int index = Integer.parseInt(command[1]) - 1;
+ if (index < 0 || index > tasks.size() - 1) {
+ throw new SerenityException("Error: Index is out of bounds.");
+ }
+ String taskDescription = parts[1].strip();
+ Task newTask = TaskList.createTask(taskDescription);
+ Task t = tasks.set(index, newTask);
+
+ return "Task has been updated! Updated task: \n " + newTask;
+ }
+
+ /**
+ * Returns string representation of TaskList.
+ *
+ * @return String representation.
+ */
+ @Override
+ public String toString() {
+ String message = "Here are the tasks in your list:";
+ for (int i = 0; i < tasks.size(); i++) {
+ int index = i + 1;
+ message += "\n" + index + ". " + tasks.get(i);
+ }
+ return message;
+ }
+
+ /**
+ * Returns string representation of TaskList to save as data.
+ *
+ * @return String representation to save as data.
+ */
+ public String formatData() {
+ String data = "";
+ for (int i = 0; i < tasks.size(); i++) {
+ data += tasks.get(i).formatData() + "\n";
+ }
+ return data;
+ }
+}
diff --git a/src/main/java/serenity/task/Todo.java b/src/main/java/serenity/task/Todo.java
new file mode 100644
index 0000000000..0176ff0b12
--- /dev/null
+++ b/src/main/java/serenity/task/Todo.java
@@ -0,0 +1,36 @@
+package serenity.task;
+
+/**
+ * Represents a type of Task with only a basic description.
+ */
+public class Todo extends Task {
+
+ /**
+ * Constructs a Todo.
+ *
+ * @param description Description of Task.
+ */
+ public Todo(String description) {
+ super(description);
+ }
+
+ /**
+ * Returns string representation of Todo.
+ *
+ * @return String representation.
+ */
+ @Override
+ public String toString() {
+ return "[T]" + super.toString();
+ }
+
+ /**
+ * Returns string representation of Todo to save as data.
+ *
+ * @return String representation to save as data.
+ */
+ @Override
+ public String formatData() {
+ return "T | " + super.formatData();
+ }
+}
diff --git a/src/main/resources/images/Serenity.png b/src/main/resources/images/Serenity.png
new file mode 100644
index 0000000000..85df6f1004
Binary files /dev/null and b/src/main/resources/images/Serenity.png differ
diff --git a/src/main/resources/images/User.png b/src/main/resources/images/User.png
new file mode 100644
index 0000000000..198b730e8d
Binary files /dev/null and b/src/main/resources/images/User.png differ
diff --git a/src/main/resources/view/DialogBox.fxml b/src/main/resources/view/DialogBox.fxml
new file mode 100644
index 0000000000..fe672c90ea
--- /dev/null
+++ b/src/main/resources/view/DialogBox.fxml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main/resources/view/MainWindow.fxml b/src/main/resources/view/MainWindow.fxml
new file mode 100644
index 0000000000..ae1140e358
--- /dev/null
+++ b/src/main/resources/view/MainWindow.fxml
@@ -0,0 +1,45 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/test/java/serenity/StorageTest.java b/src/test/java/serenity/StorageTest.java
new file mode 100644
index 0000000000..f2abe83f5d
--- /dev/null
+++ b/src/test/java/serenity/StorageTest.java
@@ -0,0 +1,53 @@
+package serenity;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import org.junit.jupiter.api.Test;
+import serenity.task.Deadline;
+import serenity.task.Event;
+import serenity.task.Todo;
+
+public class StorageTest {
+
+ @Test
+ public void loadTask_validTodo_success() throws SerenityException {
+ Storage storage = new Storage("");
+ String taskDescription = "T | 0 | read book";
+ assertEquals(new Todo("read book").toString(),
+ storage.loadTask(taskDescription).toString());
+ }
+
+ @Test
+ public void loadTask_validDeadline_success() throws SerenityException {
+ Storage storage = new Storage("");
+ String taskDescription = "D | 1 | return book | 30 Aug 2024";
+ Deadline d = new Deadline("return book", "30/08/2024");
+ d.markAsDone();
+ assertEquals(d.toString(),
+ storage.loadTask(taskDescription).toString());
+ }
+
+ @Test
+ public void loadTask_validEvent_success() throws SerenityException {
+ Storage storage = new Storage("");
+ String taskDescription = "E | 0 | attend meeting | 3pm | 5pm";
+ Event e = new Event("attend meeting", "3pm", "5pm");
+ assertEquals(e.toString(),
+ storage.loadTask(taskDescription).toString());
+ }
+
+ @Test
+ public void loadTask_invalidInput_exceptionThrown() {
+ Storage storage = new Storage("");
+ String taskDescription = "X | 0 | read book";
+ try {
+ storage.loadTask("X | 0 | read book");
+ } catch (SerenityException e) {
+ assertEquals("Error: Task cannot be loaded.",
+ e.getMessage());
+ }
+ }
+
+
+
+}
diff --git a/src/test/java/serenity/TaskListTest.java b/src/test/java/serenity/TaskListTest.java
new file mode 100644
index 0000000000..67e62c5dfe
--- /dev/null
+++ b/src/test/java/serenity/TaskListTest.java
@@ -0,0 +1,151 @@
+package serenity;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.util.ArrayList;
+
+import org.junit.jupiter.api.Test;
+import serenity.task.*;
+
+public class TaskListTest {
+
+ @Test
+ public void createTask_invalidTodo_exceptionThrown() {
+ try {
+ TaskList.createTask("todo ");
+ } catch (SerenityException e) {
+ assertEquals("Error: The description of a todo cannot be empty.",
+ e.getMessage());
+ }
+ }
+ @Test
+ public void createTask_validTodo_success() throws SerenityException {
+ Task t = TaskList.createTask("todo read book");
+ assertEquals(new Todo("read book").toString(), t.toString());
+ }
+
+ @Test
+ public void createTask_invalidTaskType_exceptionThrown() {
+ try {
+ TaskList.createTask("read book");
+ } catch (SerenityException e) {
+ assertEquals("Error: Type of task is invalid.",
+ e.getMessage());
+ }
+
+ }
+
+ @Test
+ public void addTask_validDeadline_success() throws SerenityException{
+ ArrayList tasks = new ArrayList<>();
+ TaskList taskList = new TaskList(tasks);
+ Deadline t = new Deadline("return book", "30/08/2024");
+ String expected = "Got it. I've added this task:\n"
+ + "[D][ ] return book (by: 30 Aug 2024)\n"
+ + "Now you have 1 task in the list.";
+ String actual = taskList.addTask(t);
+ assertEquals(expected, actual);
+ }
+
+ @Test
+ public void changeStatus_validIndex_success() throws SerenityException {
+ ArrayList tasks = new ArrayList<>();
+ TaskList taskList = new TaskList(tasks);
+ Todo undoneTask = new Todo("read book");
+ taskList.addTask(undoneTask);
+ taskList.changeStatus("mark 1");
+ Todo doneTask = new Todo("read book");
+ doneTask.markAsDone();
+ assertEquals(doneTask.toString(), tasks.get(0).toString());
+ }
+
+ @Test
+ public void changeStatus_missingIndex_exceptionThrown() {
+ ArrayList tasks = new ArrayList<>();
+ TaskList taskList = new TaskList(tasks);
+ Todo task = new Todo("read book");
+ taskList.addTask(task);
+ try {
+ taskList.changeStatus("mark ");
+ } catch (SerenityException e) {
+ assertEquals("Error: Missing index.",
+ e.getMessage());
+ }
+ }
+
+ @Test
+ public void changeStatus_invalidIndex_exceptionThrown() {
+ ArrayList tasks = new ArrayList<>();
+ TaskList taskList = new TaskList(tasks);
+ Todo task = new Todo("read book");
+ taskList.addTask(task);
+ try {
+ taskList.changeStatus("mark 2");
+ } catch (SerenityException e) {
+ assertEquals("Error: Index is out of bounds.",
+ e.getMessage());
+ }
+ }
+
+ @Test
+ public void updateTask_validTodo_success() throws SerenityException {
+ ArrayList tasks = new ArrayList<>();
+ TaskList taskList = new TaskList(tasks);
+ Todo task = new Todo("read book");
+ taskList.addTask(task);
+ Todo updatedTask = new Todo("read English Book");
+ taskList.updateTask("update 1 /update todo read English Book");
+ assertEquals(updatedTask.toString(), tasks.get(0).toString());
+ }
+
+ @Test
+ public void updateTask_Deadline_success() throws SerenityException {
+ ArrayList tasks = new ArrayList<>();
+ TaskList taskList = new TaskList(tasks);
+ Deadline task = new Deadline("write essay", "20/09/2024");
+ taskList.addTask(task);
+ Deadline updatedTask = new Deadline("write essay", "21/09/2024");
+ taskList.updateTask("update 1 /update deadline write essay /by 21/09/2024");
+ assertEquals(updatedTask.toString(), tasks.get(0).toString());
+ }
+
+ @Test
+ public void updateTask_Event_success() throws SerenityException {
+ ArrayList tasks = new ArrayList<>();
+ TaskList taskList = new TaskList(tasks);
+ Event task = new Event("project meeting", "2pm", "4pm");
+ taskList.addTask(task);
+ Event updatedTask = new Event("project meeting", "3pm", "5pm");
+ taskList.updateTask("update 1 /update event project meeting /from 3pm /to 5pm");
+ assertEquals(updatedTask.toString(), tasks.get(0).toString());
+ }
+
+ @Test
+ public void updateTask_missingIndex_exceptionThrown() {
+ ArrayList tasks = new ArrayList<>();
+ TaskList taskList = new TaskList(tasks);
+ Todo task = new Todo("read book");
+ taskList.addTask(task);
+ try {
+ taskList.updateTask("update /update todo read English Book");
+ } catch (SerenityException e) {
+ assertEquals("Error: Missing index.",
+ e.getMessage());
+ }
+ }
+
+ @Test
+ public void updateTask_invalidIndex_exceptionThrown() {
+ ArrayList tasks = new ArrayList<>();
+ TaskList taskList = new TaskList(tasks);
+ Todo task = new Todo("read book");
+ taskList.addTask(task);
+ try {
+ taskList.updateTask("update 2 /update todo read English Book");
+ } catch (SerenityException e) {
+ assertEquals("Error: Index is out of bounds.",
+ e.getMessage());
+ }
+ }
+
+}
diff --git a/src/test/java/serenity/TodoTest.java b/src/test/java/serenity/TodoTest.java
new file mode 100644
index 0000000000..0d8d1680dc
--- /dev/null
+++ b/src/test/java/serenity/TodoTest.java
@@ -0,0 +1,20 @@
+package serenity;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import org.junit.jupiter.api.Test;
+import serenity.task.Todo;
+
+public class TodoTest {
+
+ @Test
+ public void testToString() {
+ assertEquals("[T][ ] read book", new Todo("read book").toString());
+ }
+
+ @Test
+ public void testFormatData() {
+ assertEquals("T | 0 | read book", new Todo("read book").formatData());
+ }
+
+}
diff --git a/text-ui-test/EXPECTED.TXT b/text-ui-test/EXPECTED.TXT
index 657e74f6e7..a1b20c878a 100644
--- a/text-ui-test/EXPECTED.TXT
+++ b/text-ui-test/EXPECTED.TXT
@@ -1,7 +1,46 @@
-Hello from
- ____ _
-| _ \ _ _| | _____
-| | | | | | | |/ / _ \
-| |_| | |_| | < __/
-|____/ \__,_|_|\_\___|
-
+__________________________________________
+Hi, I'm Serenity!
+What can I do for you?
+__________________________________________
+__________________________________________
+Got it. I've added this task:
+[T][ ] read book
+Now you have 1 task in the list.
+__________________________________________
+__________________________________________
+Got it. I've added this task:
+[D][ ] return book (by: Sunday)
+Now you have 2 tasks in the list.
+__________________________________________
+__________________________________________
+Got it. I've added this task:
+[E][ ] project meeting (from: Mon 2pm to: 4pm)
+Now you have 3 tasks in the list.
+__________________________________________
+__________________________________________
+Here are the tasks in your list:
+1. [T][ ] read book
+2. [D][ ] return book (by: Sunday)
+3. [E][ ] project meeting (from: Mon 2pm to: 4pm)
+__________________________________________
+__________________________________________
+Nice! I've marked this task as done:
+[T][X] read book
+__________________________________________
+__________________________________________
+Nice! I've marked this task as done:
+[E][X] project meeting (from: Mon 2pm to: 4pm)
+__________________________________________
+__________________________________________
+OK, I've marked this task as not done yet:
+[T][ ] read book
+__________________________________________
+__________________________________________
+Here are the tasks in your list:
+1. [T][ ] read book
+2. [D][ ] return book (by: Sunday)
+3. [E][X] project meeting (from: Mon 2pm to: 4pm)
+__________________________________________
+__________________________________________
+Goodbye. Hope to see you again soon!
+__________________________________________
diff --git a/text-ui-test/input.txt b/text-ui-test/input.txt
index e69de29bb2..4a6f96b133 100644
--- a/text-ui-test/input.txt
+++ b/text-ui-test/input.txt
@@ -0,0 +1,9 @@
+todo read book
+deadline return book /by Sunday
+event project meeting /from Mon 2pm /to 4pm
+list
+mark 1
+mark 3
+unmark 1
+list
+bye
diff --git a/text-ui-test/runtest.bat b/text-ui-test/runtest.bat
index 0873744649..ba4fee27fb 100644
--- a/text-ui-test/runtest.bat
+++ b/text-ui-test/runtest.bat
@@ -15,7 +15,7 @@ IF ERRORLEVEL 1 (
REM no error here, errorlevel == 0
REM run the program, feed commands from input.txt file and redirect the output to the ACTUAL.TXT
-java -classpath ..\bin Duke < input.txt > ACTUAL.TXT
+java -classpath ..\bin Serenity < input.txt > ACTUAL.TXT
REM compare the output to the expected output
FC ACTUAL.TXT EXPECTED.TXT