diff --git a/docs/UserGuide.md b/docs/UserGuide.md
index fc9dbf8c61f..7aca2a2af09 100644
--- a/docs/UserGuide.md
+++ b/docs/UserGuide.md
@@ -928,6 +928,16 @@ For example, if your monthly budget is $1000, and you have already spent $500, y
This gives you an idea of how much of your monthly budget you have used up.
+
+
+**:exclamation: Caution**
+
+Even if you have exceeded your budget, this statistic will reflect that you have fully utilised your budget, and will remain at `100%`.
+
+
+
+
+
Back to Top
diff --git a/src/main/java/fasttrack/logic/parser/ParserUtil.java b/src/main/java/fasttrack/logic/parser/ParserUtil.java
index 1161958ca3b..4b40085232a 100644
--- a/src/main/java/fasttrack/logic/parser/ParserUtil.java
+++ b/src/main/java/fasttrack/logic/parser/ParserUtil.java
@@ -111,7 +111,7 @@ public static Category parseCategory(String categoryName) throws ParseException
public static UserDefinedCategory parseCategory(String category, String summary) throws ParseException {
requireNonNull(category);
String trimmedCategory = category.trim();
- if (!Category.isValidCategoryName(category)) {
+ if (!Category.isValidCategoryName(trimmedCategory)) {
throw new ParseException(Category.MESSAGE_CONSTRAINTS);
}
return new UserDefinedCategory(trimmedCategory, summary);
diff --git a/src/main/java/fasttrack/ui/CategoryListPanel.java b/src/main/java/fasttrack/ui/CategoryListPanel.java
index 5329f48996c..1bedc02fa46 100644
--- a/src/main/java/fasttrack/ui/CategoryListPanel.java
+++ b/src/main/java/fasttrack/ui/CategoryListPanel.java
@@ -1,8 +1,5 @@
package fasttrack.ui;
-import java.util.logging.Logger;
-
-import fasttrack.commons.core.LogsCenter;
import fasttrack.model.category.Category;
import fasttrack.model.expense.Expense;
import javafx.collections.ObservableList;
@@ -16,7 +13,6 @@
*/
public class CategoryListPanel extends UiPart {
private static final String FXML = "CategoryListPanel.fxml";
- private final Logger logger = LogsCenter.getLogger(CategoryListPanel.class);
private final ObservableList expenseObservableList;
@FXML
diff --git a/src/main/java/fasttrack/ui/CommandBox.java b/src/main/java/fasttrack/ui/CommandBox.java
index bd77f4e55cc..7aad050dbe2 100644
--- a/src/main/java/fasttrack/ui/CommandBox.java
+++ b/src/main/java/fasttrack/ui/CommandBox.java
@@ -29,19 +29,22 @@ public class CommandBox extends UiPart {
/**
* Creates a {@code CommandBox} with the given {@code CommandExecutor}.
*/
- public CommandBox(CommandExecutor commandExecutor) {
+ public CommandBox(CommandExecutor commandExecutor, boolean initialiseAutocompletion) {
super(FXML);
this.commandExecutor = commandExecutor;
// calls #setStyleToDefault() whenever there is a change to the text of the command box.
commandTextField.textProperty().addListener((unused1, unused2, unused3) -> setStyleToDefault());
- initialiseAutocompleteHandler();
+ if (initialiseAutocompletion) {
+ initialiseAutocompleteHandler();
+ }
}
+
/**
* Handles the Enter button pressed event.
*/
@FXML
- private void handleCommandEntered() {
+ public void handleCommandEntered() {
String commandText = commandTextField.getText();
if (commandText.equals("")) {
return;
diff --git a/src/main/java/fasttrack/ui/MainWindow.java b/src/main/java/fasttrack/ui/MainWindow.java
index 56ca14cb035..b398df58fe8 100644
--- a/src/main/java/fasttrack/ui/MainWindow.java
+++ b/src/main/java/fasttrack/ui/MainWindow.java
@@ -145,7 +145,7 @@ void fillInnerParts() {
StatusBarFooter statusBarFooter = new StatusBarFooter(logic.getAddressBookFilePath());
statusbarPlaceholder.getChildren().add(statusBarFooter.getRoot());
- CommandBox commandBox = new CommandBox(this::executeCommand);
+ CommandBox commandBox = new CommandBox(this::executeCommand, true);
SuggestionListPanel suggestionListPanel = new SuggestionListPanel(logic.getFilteredCategoryList(), commandBox);
diff --git a/src/test/java/fasttrack/logic/parser/ParserUtilTest.java b/src/test/java/fasttrack/logic/parser/ParserUtilTest.java
index 49ed333f6f9..43983d4d618 100644
--- a/src/test/java/fasttrack/logic/parser/ParserUtilTest.java
+++ b/src/test/java/fasttrack/logic/parser/ParserUtilTest.java
@@ -10,6 +10,8 @@
import fasttrack.commons.core.index.Index;
import fasttrack.logic.parser.exceptions.ParseException;
+import fasttrack.model.category.MiscellaneousCategory;
+import fasttrack.model.category.UserDefinedCategory;
import fasttrack.model.expense.Price;
import fasttrack.model.expense.RecurringExpenseType;
@@ -65,16 +67,16 @@ public void parsePrice_invalidInput_throwsParseException() {
assertThrows(ParseException.class, () -> ParserUtil.parsePrice("0"));
}
- //@Test
- //public void parseCategoryWithSummary_validInput_success() throws ParseException {
- // // leading and trailing whitespace
- // assertEquals(new UserDefinedCategory("category", "abc"), ParserUtil.parseCategory(" category ", "abc"));
- // // miscellaneous category
- // assertEquals(new MiscellaneousCategory(), ParserUtil.parseCategory("miscellaneous"));
- // UserDefinedCategory category = ParserUtil.parseCategory("food", "for dining");
- // assertEquals("food", category.getCategoryName());
- // assertEquals("for dining", category.getSummary());
- //}
+ @Test
+ public void parseCategoryWithSummary_validInput_success() throws ParseException {
+ // leading and trailing whitespace
+ assertEquals(new UserDefinedCategory("category", "abc"), ParserUtil.parseCategory(" category ", "abc"));
+ // miscellaneous category
+ assertEquals(new MiscellaneousCategory(), ParserUtil.parseCategory("miscellaneous"));
+ UserDefinedCategory category = ParserUtil.parseCategory("food", "for dining");
+ assertEquals("food", category.getCategoryName());
+ assertEquals("for dining", category.getSummary());
+ }
@Test
public void parseCategory_invalidInput_throwsParseException() {
diff --git a/src/test/java/fasttrack/model/util/CommandUtilityTest.java b/src/test/java/fasttrack/model/util/CommandUtilityTest.java
new file mode 100644
index 00000000000..67c4eaff0d9
--- /dev/null
+++ b/src/test/java/fasttrack/model/util/CommandUtilityTest.java
@@ -0,0 +1,28 @@
+package fasttrack.model.util;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import java.time.LocalDate;
+
+import org.junit.jupiter.api.Test;
+
+class CommandUtilityTest {
+
+ @Test
+ void testParseDateFromUserInput_validFormats_success() {
+ assertEquals(LocalDate.of(2023, 3, 1), CommandUtility.parseDateFromUserInput("1/3/23"));
+ assertEquals(LocalDate.of(2023, 6, 5), CommandUtility.parseDateFromUserInput("5/6/2023"));
+ assertEquals(LocalDate.of(2022, 7, 16), CommandUtility.parseDateFromUserInput("16/7/22"));
+ assertEquals(LocalDate.of(2021, 12, 31), CommandUtility.parseDateFromUserInput("31/12/2021"));
+ }
+ @Test
+ void testParseDateFromUserInput_invalidFormats_throwsException() {
+ assertThrows(IllegalArgumentException.class, () -> CommandUtility.parseDateFromUserInput("32/2/2022"));
+ assertThrows(IllegalArgumentException.class, () -> CommandUtility.parseDateFromUserInput("12/13/2023"));
+ assertThrows(IllegalArgumentException.class, () -> CommandUtility.parseDateFromUserInput("12w/12/23a"));
+ assertThrows(IllegalArgumentException.class, () -> CommandUtility.parseDateFromUserInput("12/c12/23a"));
+ assertThrows(IllegalArgumentException.class, () -> CommandUtility.parseDateFromUserInput("2023-4-31"));
+ }
+
+}
diff --git a/src/test/java/fasttrack/model/util/StorageUtilityTest.java b/src/test/java/fasttrack/model/util/StorageUtilityTest.java
new file mode 100644
index 00000000000..1c498fff157
--- /dev/null
+++ b/src/test/java/fasttrack/model/util/StorageUtilityTest.java
@@ -0,0 +1,26 @@
+package fasttrack.model.util;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import java.time.LocalDate;
+
+import org.junit.jupiter.api.Test;
+
+
+public class StorageUtilityTest {
+
+ @Test
+ public void testParseDateFromJson_validDate() {
+ LocalDate expectedDate = LocalDate.of(2023, 4, 30);
+ LocalDate parsedDate = StorageUtility.parseDateFromJson("2023-04-30");
+ assertEquals(expectedDate, parsedDate);
+ }
+
+ @Test
+ public void testParseDateFromJson_invalidDate() {
+ assertThrows(java.time.format.DateTimeParseException.class, () -> {
+ StorageUtility.parseDateFromJson("20-4-31");
+ });
+ }
+}
diff --git a/src/test/java/fasttrack/model/util/UserInterfaceUtilTest.java b/src/test/java/fasttrack/model/util/UserInterfaceUtilTest.java
new file mode 100644
index 00000000000..97f48ee062c
--- /dev/null
+++ b/src/test/java/fasttrack/model/util/UserInterfaceUtilTest.java
@@ -0,0 +1,52 @@
+package fasttrack.model.util;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.time.LocalDate;
+
+import org.junit.jupiter.api.Test;
+
+public class UserInterfaceUtilTest {
+
+ @Test
+ public void testParseDate_validDate_success() {
+ LocalDate date1 = LocalDate.of(2023, 4, 10);
+ String formattedDate1 = UserInterfaceUtil.parseDate(date1);
+ assertEquals("10/04/23", formattedDate1);
+ LocalDate date2 = LocalDate.of(2023, 12, 31);
+ String formattedDate2 = UserInterfaceUtil.parseDate(date2);
+ assertEquals("31/12/23", formattedDate2);
+ }
+
+ @Test
+ public void testParsePrice_validInput_success() {
+ double amount1 = 10.00;
+ String formattedAmount1 = UserInterfaceUtil.parsePrice(amount1);
+ assertEquals("$10.00", formattedAmount1);
+
+ double amount2 = -5.50;
+ String formattedAmount2 = UserInterfaceUtil.parsePrice(amount2);
+ assertEquals("$-5.50", formattedAmount2);
+
+ double amount3 = 3.14159;
+ String formattedAmount3 = UserInterfaceUtil.parsePrice(amount3);
+ assertEquals("$3.14", formattedAmount3);
+ }
+
+ @Test
+ public void testCapitalizeFirstLetter_validInput_success() {
+ String input1 = "hello";
+ String capitalized1 = UserInterfaceUtil.capitalizeFirstLetter(input1);
+ assertEquals("Hello", capitalized1);
+
+ String input2 = "wORLD";
+ String capitalized2 = UserInterfaceUtil.capitalizeFirstLetter(input2);
+ assertEquals("WORLD", capitalized2);
+
+ String input3 = "TEST";
+ String capitalized3 = UserInterfaceUtil.capitalizeFirstLetter(input3);
+ assertEquals("TEST", capitalized3);
+ }
+
+}
+
diff --git a/src/test/java/fasttrack/ui/CategoryCardTest.java b/src/test/java/fasttrack/ui/CategoryCardTest.java
new file mode 100644
index 00000000000..9a5e3c1c5b2
--- /dev/null
+++ b/src/test/java/fasttrack/ui/CategoryCardTest.java
@@ -0,0 +1,87 @@
+package fasttrack.ui;
+
+import static fasttrack.testutil.TypicalCategories.FOOD;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.fail;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.opentest4j.AssertionFailedError;
+
+import fasttrack.model.category.Category;
+import javafx.application.Platform;
+import javafx.embed.swing.JFXPanel;
+import javafx.scene.control.Label;
+
+
+public class CategoryCardTest {
+
+ private Category category;
+ private int displayedIndex;
+ private int associatedExpenseCount;
+
+ @BeforeEach
+ public void setUp() {
+ category = FOOD;
+ displayedIndex = 1;
+ associatedExpenseCount = 3;
+ // Initialise fake JavaFX environment
+ new JFXPanel();
+ }
+
+ @Test
+ public void testCategoryCard_validData_success() {
+ CategoryCard categoryCard = new CategoryCard(category, displayedIndex, associatedExpenseCount);
+ CompletableFuture future = new CompletableFuture<>();
+ Platform.runLater(() -> {
+ try {
+ // Test that the category name label is set correctly
+ Label categoryNameLabel = (Label) categoryCard.getRoot().lookup("#categoryName");
+ assertEquals("Food", categoryNameLabel.getText());
+
+ // Test that the index label is set correctly
+ Label indexLabel = (Label) categoryCard.getRoot().lookup("#id");
+ assertEquals("1. ", indexLabel.getText());
+
+ // Test that the expense count label is set correctly
+ Label expenseCountLabel = (Label) categoryCard.getRoot().lookup("#expenseCount");
+ assertEquals("3", expenseCountLabel.getText());
+ future.complete(null);
+ } catch (AssertionFailedError e) {
+ future.completeExceptionally(e);
+ }
+ });
+ try {
+ future.get();
+ } catch (InterruptedException | ExecutionException e) {
+ fail("Assertion error thrown in Platform.runLater thread: " + e.getMessage());
+ }
+ }
+
+ @Test
+ public void testEquals_validCategoryCard_success() {
+ CategoryCard categoryCard1 = new CategoryCard(category, displayedIndex, associatedExpenseCount);
+ CategoryCard categoryCard2 = new CategoryCard(category, displayedIndex + 1, associatedExpenseCount - 1);
+ CategoryCard categoryCard3 = new CategoryCard(category, displayedIndex + 1, associatedExpenseCount - 1);
+ CompletableFuture future = new CompletableFuture<>();
+ Platform.runLater(() -> {
+ try {
+ assertEquals(categoryCard1, categoryCard1);
+ assertNotEquals(categoryCard1, categoryCard2);
+ assertEquals(categoryCard2, categoryCard3);
+ future.complete(null);
+ } catch (AssertionFailedError e) {
+ future.completeExceptionally(e);
+ }
+ });
+ try {
+ future.get();
+ } catch (InterruptedException | ExecutionException e) {
+ fail("Assertion error thrown in Platform.runLater thread: " + e.getMessage());
+ }
+ }
+}
diff --git a/src/test/java/fasttrack/ui/CategoryListPanelTest.java b/src/test/java/fasttrack/ui/CategoryListPanelTest.java
new file mode 100644
index 00000000000..1a8fcb0ac3d
--- /dev/null
+++ b/src/test/java/fasttrack/ui/CategoryListPanelTest.java
@@ -0,0 +1,82 @@
+package fasttrack.ui;
+
+import static fasttrack.testutil.TypicalCategories.FOOD;
+import static fasttrack.testutil.TypicalCategories.TECH;
+import static fasttrack.testutil.TypicalExpenses.APPLE;
+import static fasttrack.testutil.TypicalExpenses.CHERRY;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.fail;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.opentest4j.AssertionFailedError;
+
+import fasttrack.model.category.Category;
+import fasttrack.model.expense.Expense;
+import javafx.application.Platform;
+import javafx.collections.FXCollections;
+import javafx.collections.ObservableList;
+import javafx.embed.swing.JFXPanel;
+import javafx.scene.control.ListView;
+
+
+public class CategoryListPanelTest {
+
+ private CategoryListPanel categoryListPanel;
+ private ObservableList categories;
+ private ObservableList expenses;
+
+ @BeforeEach
+ public void setUp() {
+ categories = FXCollections.observableArrayList(FOOD, TECH);
+ expenses = FXCollections.observableArrayList(APPLE, CHERRY);
+ // Initialise fake JavaFX environment
+ new JFXPanel();
+ }
+
+ @Test
+ public void categoryListView_validCategories_countEqual() {
+ CompletableFuture future = new CompletableFuture<>();
+ categoryListPanel = new CategoryListPanel(categories, expenses);
+ Platform.runLater(() -> {
+ try {
+ // Test that the number of categories is correct
+ ListView> categoryListView = (ListView>) categoryListPanel.getRoot().lookup("#categoryListView");
+ assertEquals(categories.size(), categoryListView.getItems().size());
+ future.complete(null);
+ } catch (AssertionFailedError e) {
+ future.completeExceptionally(e);
+ }
+ });
+ try {
+ future.get();
+ } catch (InterruptedException | ExecutionException e) {
+ fail("Assertion error thrown in Platform.runLater thread: " + e.getMessage());
+ }
+ }
+
+ @Test
+ public void categoryListView_emptyList_countZero() {
+ categories = FXCollections.observableArrayList();
+ expenses = FXCollections.observableArrayList();
+ CompletableFuture future = new CompletableFuture<>();
+ categoryListPanel = new CategoryListPanel(categories, expenses);
+ Platform.runLater(() -> {
+ try {
+ ListView> categoryListView = (ListView>) categoryListPanel.getRoot().lookup("#categoryListView");
+ assertEquals(0, categoryListView.getItems().size());
+ future.complete(null);
+ } catch (AssertionFailedError e) {
+ future.completeExceptionally(e);
+ }
+ });
+ try {
+ future.get();
+ } catch (InterruptedException | ExecutionException e) {
+ fail("Assertion error thrown in Platform.runLater thread: " + e.getMessage());
+ }
+ }
+}
diff --git a/src/test/java/fasttrack/ui/CommandBoxTest.java b/src/test/java/fasttrack/ui/CommandBoxTest.java
new file mode 100644
index 00000000000..7df8ff03460
--- /dev/null
+++ b/src/test/java/fasttrack/ui/CommandBoxTest.java
@@ -0,0 +1,88 @@
+package fasttrack.ui;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.util.Objects;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import fasttrack.logic.commands.exceptions.CommandException;
+import javafx.collections.ObservableList;
+import javafx.embed.swing.JFXPanel;
+import javafx.scene.Scene;
+import javafx.scene.control.TextField;
+import javafx.scene.layout.HBox;
+
+public class CommandBoxTest {
+
+ private static final String VALID_COMMAND = "add n/Milk c/Groceries p/12";
+ private static final String INCOMPLETE_COMMAND = "add n/Milk c/Gro";
+ private static final String ERROR_STYLE_CLASS = "error";
+
+ private CommandBox commandBox;
+ private boolean commandExecuted;
+
+
+ @BeforeEach
+ public void setUp() {
+ new JFXPanel();
+ commandExecuted = false;
+ // Dummy command executor function
+ CommandBox.CommandExecutor commandExecutor = commandText -> {
+ if (Objects.equals(commandText, "invalid command")) {
+ throw new CommandException("Invalid command");
+ }
+ commandExecuted = true;
+ return null;
+ };
+ commandBox = new CommandBox(commandExecutor, false);
+ }
+
+ @Test
+ public void handleCommandEntered_emptyCommand_commandNotExecuted() {
+ TextField textField = (TextField) commandBox.getRoot().lookup("#commandTextField");
+ textField.setText("");
+ commandBox.handleCommandEntered();
+ assertFalse(commandExecuted);
+ }
+
+ @Test
+ public void handleCommandEntered_validCommand_commandExecutedSuccessfully() {
+ TextField textField = (TextField) commandBox.getRoot().lookup("#commandTextField");
+ textField.setText(VALID_COMMAND);
+ commandBox.handleCommandEntered();
+ assertTrue(commandExecuted);
+ }
+
+ @Test
+ public void handleCommandEntered_invalidCommand_setsStyleToIndicateCommandFailure() {
+ commandBox.getTextProperty().setValue("invalid command");
+ commandBox.handleCommandEntered();
+ TextField commandTextField = (TextField) commandBox.getRoot().lookup("#commandTextField");
+ ObservableList styleClass = commandTextField.getStyleClass();
+ assertTrue(styleClass.contains(ERROR_STYLE_CLASS));
+ }
+
+ @Test
+ public void setFocus_commandBox_getsFocus() {
+ TextField textField = (TextField) commandBox.getRoot().lookup("#commandTextField");
+ HBox hbox = new HBox();
+ hbox.getChildren().add(commandBox.getRoot());
+ Scene scene = new Scene(hbox);
+ commandBox.setFocus();
+ TextField focusedTextField = (TextField) scene.getFocusOwner();
+ assertEquals(focusedTextField, textField);
+ }
+
+ @Test
+ public void updateCommandBoxText_validCategoryName_updatesText() {
+ TextField textField = (TextField) commandBox.getRoot().lookup("#commandTextField");
+ textField.setText(INCOMPLETE_COMMAND);
+ commandBox.updateCommandBoxText("Groceries");
+ String expected = "add n/Milk c/Groceries ";
+ assertEquals(expected, textField.getText());
+ }
+}
diff --git a/src/test/java/fasttrack/ui/ExpenseCardTest.java b/src/test/java/fasttrack/ui/ExpenseCardTest.java
new file mode 100644
index 00000000000..ca8c643cf9a
--- /dev/null
+++ b/src/test/java/fasttrack/ui/ExpenseCardTest.java
@@ -0,0 +1,93 @@
+package fasttrack.ui;
+
+import static fasttrack.testutil.TypicalExpenses.APPLE;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.fail;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.opentest4j.AssertionFailedError;
+
+import fasttrack.model.expense.Expense;
+import javafx.application.Platform;
+import javafx.embed.swing.JFXPanel;
+import javafx.scene.control.Label;
+
+class ExpenseCardTest {
+
+ private Expense expense;
+ private int displayedIndex;
+
+ @BeforeEach
+ public void setUp() {
+ expense = APPLE;
+ displayedIndex = 1;
+ // Initialise fake JavaFX environment
+ new JFXPanel();
+ }
+
+ @Test
+ public void testExpenseCard_validData_success() {
+ ExpenseCard expenseCard = new ExpenseCard(expense, displayedIndex);
+ CompletableFuture future = new CompletableFuture<>();
+ Platform.runLater(() -> {
+ try {
+ // Test that the category name label is set correctly
+ Label expenseNameLabel = (Label) expenseCard.getRoot().lookup("#expenseName");
+ assertEquals("Apple", expenseNameLabel.getText());
+
+ // Test that the index label is set correctly
+ Label indexLabel = (Label) expenseCard.getRoot().lookup("#id");
+ assertEquals("1. ", indexLabel.getText());
+
+ // Test that the price label is set correctly
+ Label priceLabel = (Label) expenseCard.getRoot().lookup("#price");
+ assertEquals("$1.50", priceLabel.getText());
+
+ // Test that the category label is set correctly
+ Label categoryLabel = (Label) expenseCard.getRoot().lookup("#category");
+ assertEquals("Food", categoryLabel.getText());
+
+ // Test that the date label is set correctly
+ Label frequencyLabel = (Label) expenseCard.getRoot().lookup("#date");
+ assertEquals("01/03/23", frequencyLabel.getText());
+ future.complete(null);
+ } catch (AssertionFailedError e) {
+ future.completeExceptionally(e);
+ }
+ });
+ try {
+ future.get();
+ } catch (InterruptedException | ExecutionException e) {
+ fail("Assertion error thrown in Platform.runLater thread: " + e.getMessage());
+ }
+ }
+
+ @Test
+ public void testEquals_validExpenseCard_success() {
+ ExpenseCard expenseCard1 = new ExpenseCard(expense, displayedIndex);
+ ExpenseCard expenseCard2 = new ExpenseCard(expense, displayedIndex + 1);
+ ExpenseCard expenseCard3 = new ExpenseCard(expense, displayedIndex + 1);
+ CompletableFuture future = new CompletableFuture<>();
+ Platform.runLater(() -> {
+ try {
+ assertEquals(expenseCard1, expenseCard1);
+ assertNotEquals(expenseCard1, expenseCard2);
+ assertEquals(expenseCard2, expenseCard3);
+ future.complete(null);
+ } catch (AssertionFailedError e) {
+ future.completeExceptionally(e);
+ }
+ });
+ try {
+ future.get();
+ } catch (InterruptedException | ExecutionException e) {
+ fail("Assertion error thrown in Platform.runLater thread: " + e.getMessage());
+ }
+ }
+
+}
diff --git a/src/test/java/fasttrack/ui/ExpenseListPanelTest.java b/src/test/java/fasttrack/ui/ExpenseListPanelTest.java
new file mode 100644
index 00000000000..b63e2aa5989
--- /dev/null
+++ b/src/test/java/fasttrack/ui/ExpenseListPanelTest.java
@@ -0,0 +1,77 @@
+package fasttrack.ui;
+
+import static fasttrack.testutil.TypicalExpenses.APPLE;
+import static fasttrack.testutil.TypicalExpenses.BANANA;
+import static fasttrack.testutil.TypicalExpenses.CHERRY;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.fail;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.opentest4j.AssertionFailedError;
+
+import fasttrack.model.expense.Expense;
+import javafx.application.Platform;
+import javafx.collections.FXCollections;
+import javafx.collections.ObservableList;
+import javafx.embed.swing.JFXPanel;
+import javafx.scene.control.ListView;
+
+
+class ExpenseListPanelTest {
+
+ private ExpenseListPanel expensePanel;
+ private ObservableList expenses;
+
+ @BeforeEach
+ public void setUp() {
+ expenses = FXCollections.observableArrayList(APPLE, BANANA, CHERRY);
+ // Initialise fake JavaFX environment
+ new JFXPanel();
+ }
+
+ @Test
+ public void expenseListView_validExpenses_countEqual() {
+ expensePanel = new ExpenseListPanel(expenses);
+ CompletableFuture future = new CompletableFuture<>();
+ Platform.runLater(() -> {
+ try {
+ // Test that the number of recurring expenses is correct
+ ListView> expenseListView = (ListView>) expensePanel.getRoot().lookup("#expenseListView");
+ assertEquals(expenses.size(), expenseListView.getItems().size());
+ future.complete(null);
+ } catch (AssertionFailedError e) {
+ future.completeExceptionally(e);
+ }
+ });
+ try {
+ future.get();
+ } catch (InterruptedException | ExecutionException e) {
+ fail("Assertion error thrown in Platform.runLater thread: " + e.getMessage());
+ }
+ }
+
+ @Test
+ public void expenseListView_validExpenses_countZero() {
+ expenses = FXCollections.observableArrayList();
+ expensePanel = new ExpenseListPanel(expenses);
+ CompletableFuture future = new CompletableFuture<>();
+ Platform.runLater(() -> {
+ try {
+ ListView> expenseListView = (ListView>) expensePanel.getRoot().lookup("#expenseListView");
+ assertEquals(0, expenseListView.getItems().size());
+ future.complete(null);
+ } catch (AssertionFailedError e) {
+ future.completeExceptionally(e);
+ }
+ });
+ try {
+ future.get();
+ } catch (InterruptedException | ExecutionException e) {
+ fail("Assertion error thrown in Platform.runLater thread: " + e.getMessage());
+ }
+ }
+}
diff --git a/src/test/java/fasttrack/ui/RecurringExpenseCardTest.java b/src/test/java/fasttrack/ui/RecurringExpenseCardTest.java
new file mode 100644
index 00000000000..aa57e4dc235
--- /dev/null
+++ b/src/test/java/fasttrack/ui/RecurringExpenseCardTest.java
@@ -0,0 +1,98 @@
+package fasttrack.ui;
+
+import static fasttrack.testutil.TypicalRecurringExpenses.GYM_MEMBERSHIP;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.fail;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.opentest4j.AssertionFailedError;
+
+import fasttrack.model.expense.RecurringExpenseManager;
+import javafx.application.Platform;
+import javafx.embed.swing.JFXPanel;
+import javafx.scene.control.Label;
+
+class RecurringExpenseCardTest {
+
+ private RecurringExpenseManager recurringExpenseManager;
+ private int displayedIndex;
+
+ @BeforeEach
+ public void setUp() {
+ recurringExpenseManager = GYM_MEMBERSHIP;
+ displayedIndex = 1;
+ // Initialise fake JavaFX environment
+ new JFXPanel();
+ }
+
+ @Test
+ public void testRecurringExpenseCard_validData_success() {
+ RecurringExpenseCard recurringExpenseCard = new RecurringExpenseCard(
+ recurringExpenseManager, displayedIndex);
+ CompletableFuture future = new CompletableFuture<>();
+ Platform.runLater(() -> {
+ try {
+ // Test that the category name label is set correctly
+ Label expenseNameLabel = (Label) recurringExpenseCard.getRoot().lookup("#expenseName");
+ assertEquals("Gym Membership", expenseNameLabel.getText());
+
+ // Test that the index label is set correctly
+ Label indexLabel = (Label) recurringExpenseCard.getRoot().lookup("#id");
+ assertEquals("1. ", indexLabel.getText());
+
+ // Test that the price label is set correctly
+ Label priceLabel = (Label) recurringExpenseCard.getRoot().lookup("#price");
+ assertEquals("$50.00", priceLabel.getText());
+
+ // Test that the category label is set correctly
+ Label categoryLabel = (Label) recurringExpenseCard.getRoot().lookup("#category");
+ assertEquals("Fitness", categoryLabel.getText());
+
+ // Test that the frequency label is set correctly
+ Label frequencyLabel = (Label) recurringExpenseCard.getRoot().lookup("#frequency");
+ assertEquals("Monthly", frequencyLabel.getText());
+ future.complete(null);
+ } catch (AssertionFailedError e) {
+ future.completeExceptionally(e);
+ }
+ });
+ try {
+ future.get();
+ } catch (InterruptedException | ExecutionException e) {
+ fail("Assertion error thrown in Platform.runLater thread: " + e.getMessage());
+ }
+ }
+
+ @Test
+ public void testEquals_validRecurringExpenseCard_success() {
+ RecurringExpenseCard recurringExpenseCard1 = new RecurringExpenseCard(
+ recurringExpenseManager, displayedIndex);
+ RecurringExpenseCard recurringExpenseCard2 = new RecurringExpenseCard(
+ recurringExpenseManager, displayedIndex + 1);
+ RecurringExpenseCard recurringExpenseCard3 = new RecurringExpenseCard(
+ recurringExpenseManager, displayedIndex + 1);
+ CompletableFuture future = new CompletableFuture<>();
+
+ Platform.runLater(() -> {
+ try {
+ assertEquals(recurringExpenseCard1, recurringExpenseCard1);
+ assertNotEquals(recurringExpenseCard1, recurringExpenseCard2);
+ assertEquals(recurringExpenseCard2, recurringExpenseCard3);
+ future.complete(null);
+ } catch (AssertionFailedError e) {
+ future.completeExceptionally(e);
+ }
+ });
+ try {
+ future.get();
+ } catch (InterruptedException | ExecutionException e) {
+ fail("Assertion error thrown in Platform.runLater thread: " + e.getMessage());
+ }
+ }
+
+}
diff --git a/src/test/java/fasttrack/ui/RecurringExpensePanelTest.java b/src/test/java/fasttrack/ui/RecurringExpensePanelTest.java
new file mode 100644
index 00000000000..8b3d76197ac
--- /dev/null
+++ b/src/test/java/fasttrack/ui/RecurringExpensePanelTest.java
@@ -0,0 +1,80 @@
+package fasttrack.ui;
+
+import static fasttrack.testutil.TypicalRecurringExpenses.GYM_MEMBERSHIP;
+import static fasttrack.testutil.TypicalRecurringExpenses.NETFLIX_SUBSCRIPTION;
+import static fasttrack.testutil.TypicalRecurringExpenses.RENT;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.fail;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.opentest4j.AssertionFailedError;
+
+import fasttrack.model.expense.RecurringExpenseManager;
+import javafx.application.Platform;
+import javafx.collections.FXCollections;
+import javafx.collections.ObservableList;
+import javafx.embed.swing.JFXPanel;
+import javafx.scene.control.ListView;
+
+
+class RecurringExpensePanelTest {
+
+ private RecurringExpensePanel recurringExpensePanel;
+ private ObservableList recurringExpenses;
+
+ @BeforeEach
+ public void setUp() {
+ recurringExpenses = FXCollections.observableArrayList(GYM_MEMBERSHIP, NETFLIX_SUBSCRIPTION, RENT);
+ // Initialise fake JavaFX environment
+ new JFXPanel();
+ }
+
+ @Test
+ public void recurringExpenseListView_validExpenses_countEqual() {
+ recurringExpensePanel = new RecurringExpensePanel(recurringExpenses);
+ CompletableFuture future = new CompletableFuture<>();
+ Platform.runLater(() -> {
+ try {
+ // Test that the number of recurring expenses is correct
+ ListView> recurringExpenseListView = (ListView>) recurringExpensePanel
+ .getRoot().lookup("#recurringExpenseListView");
+ assertEquals(recurringExpenses.size(), recurringExpenseListView.getItems().size());
+ future.complete(null);
+ } catch (AssertionFailedError e) {
+ future.completeExceptionally(e);
+ }
+ });
+ try {
+ future.get();
+ } catch (InterruptedException | ExecutionException e) {
+ fail("Assertion error thrown in Platform.runLater thread: " + e.getMessage());
+ }
+ }
+
+ @Test
+ public void recurringExpenseListView_validExpenses_countZero() {
+ recurringExpenses = FXCollections.observableArrayList();
+ RecurringExpensePanel recurringExpensePanel = new RecurringExpensePanel(recurringExpenses);
+ CompletableFuture future = new CompletableFuture<>();
+ Platform.runLater(() -> {
+ try {
+ ListView> recurringExpenseListView = (ListView>) recurringExpensePanel
+ .getRoot().lookup("#recurringExpenseListView");
+ assertEquals(0, recurringExpenseListView.getItems().size());
+ future.complete(null);
+ } catch (AssertionFailedError e) {
+ future.completeExceptionally(e);
+ }
+ });
+ try {
+ future.get();
+ } catch (InterruptedException | ExecutionException e) {
+ fail("Assertion error thrown in Platform.runLater thread: " + e.getMessage());
+ }
+ }
+
+}