diff --git a/docs/UserGuide.md b/docs/UserGuide.md index 251a5944745..3cdf1f48280 100644 --- a/docs/UserGuide.md +++ b/docs/UserGuide.md @@ -265,8 +265,9 @@ This feature allows users to update the details of an existing customer in the d This feature allows users to search for customers by specific details such as name, address, email, phone number, job title, or remarks. **How to Use It:** -To perform a search, use the `filter` command followed by one or more flags (indicating the fields to search) and the corresponding search terms. - Searches are **case-insensitive** and use [**substring-matching**](#substring-matching), **except for Tier**, which must start with the specified substring. +To perform a search, use the `filter` command followed by one or more flags (indicating the fields to search) and the corresponding search terms. + +Searches are **case-insensitive** and use [**substring-matching**](#substring-matching), **except for [Tier](#filtering-by-tier) and [Income](#filtering-by-income)**, which have their own specific matching criteria detailed below. - **Command Format:** ``` @@ -288,10 +289,10 @@ To perform a search, use the `filter` command followed by one or more flags (ind #### Parameters -| Parameter | Expected Format | Explanation | -|-------------|--------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------| -| FLAG | Refer to the list of supported flags detailed below. | Identifies the field to search (e.g., `n/` for name, `j/` for job). | | -| SEARCH TERM | Refer to the syntax constraints in the [parameter subsection of the `add` command](#add-command-parameters). | The value to search for in the specified field (e.g., "doctor" for job, "TAN LESHEW" for name). | +| Parameter | Expected Format | Explanation | +|-------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------| +| FLAG | Refer to the list of supported flags detailed below. | Identifies the field to search.

e.g., `n/` for name, `j/` for job. | | +| SEARCH TERM | Follows the syntax for [each field's expected input](#add-command-parameters).

**Income** requires a numeric value with a comparison operator (`=`, `>`, `<`), while **Tier** allows for partial (prefix) matching. Other fields follow substring matching. | The value to search for in the specified field.

e.g., `doctor` for job, `>5000` for income). | #### Supported flags: - `n/` for Name @@ -306,6 +307,19 @@ To perform a search, use the `filter` command followed by one or more flags (ind - Substring matching is used for searches, meaning that the search term must match a part of the field in the same order as it appears in the customer record. - For instance, if a customer’s name is `Gordon Moore`, the search term `Gordon`, `Moore`, or `Gordon Moore` will match, but `Moore Gordon` will not. +#### Filtering By Tier +- **Prefix Matching:** Tier searches use **prefix matching**, meaning the search term must match the beginning of the tier exactly. + - If a customer has a tier labeled `Gold`, a search for `t/ G` or `t/ Gold` will match, but `t/ ld` or `t/ Gold Premium` will not. + +#### Filtering By Income +- **Comparison Operators:** Filtering by income allows numeric comparisons using operators `=`, `>`, or `<` to find customers whose income meets certain criteria. +- **Equal to (`=`):** Use `=` to find customers with a specific income. + - `i/ =5000` will match customers with an income of exactly 5000. +- **Greater than (`>`):** Use `>` to find customers with an income higher than the specified threshold. + - `i/ >5000` will match customers with incomes greater than 5000. +- **Less than (`<`):** Use `<` to find customers with an income lower than the specified threshold. + - `i/ <5000` will match customers with incomes below 5000. + #### What to Expect - **If Successful:** - Message: "`x` person listed!", where `x` is the number of matching results. diff --git a/src/main/java/seedu/address/logic/parser/FilterCommandParser.java b/src/main/java/seedu/address/logic/parser/FilterCommandParser.java index f649da17008..1e2dbfcc1ba 100644 --- a/src/main/java/seedu/address/logic/parser/FilterCommandParser.java +++ b/src/main/java/seedu/address/logic/parser/FilterCommandParser.java @@ -23,11 +23,13 @@ import seedu.address.model.person.predicates.AddressContainsSubstringPredicate; import seedu.address.model.person.predicates.CombinedPredicate; import seedu.address.model.person.predicates.EmailContainsSubstringPredicate; +import seedu.address.model.person.predicates.IncomeComparisonPredicate; import seedu.address.model.person.predicates.JobContainsSubstringPredicate; import seedu.address.model.person.predicates.NameContainsSubstringPredicate; import seedu.address.model.person.predicates.PhoneContainsSubstringPredicate; import seedu.address.model.person.predicates.RemarkContainsSubstringPredicate; import seedu.address.model.person.predicates.TierStartsWithSubstringPredicate; +import seedu.address.model.util.IncomeComparisonOperator; /** * Parses input arguments and creates a new FilterCommand object @@ -110,6 +112,15 @@ private List> collectPredicates(ArgumentMultimap argMultimap) String substring = ParserUtil.parseJob(argMultimap.getValue(PREFIX_JOB).get()).value; predicates.add(new JobContainsSubstringPredicate(substring)); } + if (argMultimap.getValue(PREFIX_INCOME).isPresent()) { + String operatorAndIncome = argMultimap.getValue(PREFIX_INCOME).get(); + + IncomeComparisonOperator operator = + ParserUtil.parseIncomeComparisonOperator(operatorAndIncome.substring(0, 1)); + int income = ParserUtil.parseIncome(operatorAndIncome.substring(1)).value; + + predicates.add(new IncomeComparisonPredicate(operator, income)); + } if (argMultimap.getValue(PREFIX_REMARK).isPresent()) { String substring = ParserUtil.parseRemark(argMultimap.getValue(PREFIX_REMARK).get()).value; predicates.add(new RemarkContainsSubstringPredicate(substring)); diff --git a/src/main/java/seedu/address/logic/parser/ParserUtil.java b/src/main/java/seedu/address/logic/parser/ParserUtil.java index c9ac8ca6743..61283eb8269 100644 --- a/src/main/java/seedu/address/logic/parser/ParserUtil.java +++ b/src/main/java/seedu/address/logic/parser/ParserUtil.java @@ -13,6 +13,7 @@ import seedu.address.model.person.Phone; import seedu.address.model.person.Remark; import seedu.address.model.tier.Tier; +import seedu.address.model.util.IncomeComparisonOperator; /** * Contains utility methods used for parsing strings in the various *Parser classes. @@ -141,10 +142,25 @@ public static Remark parseRemark(String remark) throws ParseException { } /** - * Parses a {@code String tag} into a {@code Tag}. + * Parses a {@code String remark} into a {@code Remark}. + * Leading and trailing whitespaces are trimmed. + * + * @throws ParseException if the given {@code remark} is invalid. + */ + public static Remark parseNewRemark(String remark) throws ParseException { + requireNonNull(remark); + String trimmedRemark = remark.trim(); + if (!Remark.isValidRemark(trimmedRemark)) { + throw new ParseException(Remark.MESSAGE_CONSTRAINTS); + } + return new Remark(trimmedRemark); + } + + /** + * Parses a {@code String tier} into a {@code Tier}. * Leading and trailing whitespaces will be trimmed. * - * @throws ParseException if the given {@code tag} is invalid. + * @throws ParseException if the given {@code tier} is invalid. */ public static Tier parseTier(String tag) throws ParseException { requireNonNull(tag); @@ -159,19 +175,19 @@ public static Tier parseTier(String tag) throws ParseException { } /** - * Parses a {@code String remark} into a {@code Remark}. - * Leading and trailing whitespaces are trimmed. + * Parses a {@code String operator} into a {@code IncomeComparisonOperator}. + * Leading and trailing whitespaces will be trimmed. * - * @throws ParseException if the given {@code remark} is invalid. + * @throws ParseException if the given {@code operator} is invalid. */ - public static Remark parseNewRemark(String remark) throws ParseException { - requireNonNull(remark); - String trimmedRemark = remark.trim(); - if (!Remark.isValidRemark(trimmedRemark)) { - System.out.println("exception thrown"); - throw new ParseException(Remark.MESSAGE_CONSTRAINTS); + public static IncomeComparisonOperator parseIncomeComparisonOperator(String operator) throws ParseException { + requireNonNull(operator); + String trimmedOperator = operator.trim(); + if (!IncomeComparisonOperator.isValidComparisonOperator(trimmedOperator)) { + System.out.println(("HERE")); + throw new ParseException(IncomeComparisonOperator.MESSAGE_CONSTRAINTS); } - return new Remark(trimmedRemark); + return new IncomeComparisonOperator(trimmedOperator); } } diff --git a/src/main/java/seedu/address/model/person/predicates/IncomeComparisonPredicate.java b/src/main/java/seedu/address/model/person/predicates/IncomeComparisonPredicate.java new file mode 100644 index 00000000000..37f2485d5be --- /dev/null +++ b/src/main/java/seedu/address/model/person/predicates/IncomeComparisonPredicate.java @@ -0,0 +1,84 @@ +package seedu.address.model.person.predicates; + +import static java.util.Objects.requireNonNull; + +import java.util.function.Predicate; + +import seedu.address.commons.util.ToStringBuilder; +import seedu.address.model.person.Person; +import seedu.address.model.util.IncomeComparisonOperator; + +/** + * Predicate that compares a {@code Person}'s income against a threshold using a specified comparison operator. + */ +public class IncomeComparisonPredicate implements Predicate { + private final int incomeThreshold; + private final IncomeComparisonOperator incomeComparisonOperator; + + /** + * Constructs an {@code IncomeComparisonPredicate}. + * + * @param incomeComparisonOperator The operator used to compare the person's income with the threshold. + * @param incomeThreshold The threshold income to compare against. + */ + public IncomeComparisonPredicate(IncomeComparisonOperator incomeComparisonOperator, int incomeThreshold) { + requireNonNull(incomeComparisonOperator); + checkPositiveIncomeThreshold(incomeThreshold); + this.incomeThreshold = incomeThreshold; + this.incomeComparisonOperator = incomeComparisonOperator; + } + + @Override + public boolean test(Person person) { + int personIncome = person.getIncome().value; + + switch (incomeComparisonOperator.comparisonOperator) { + case "=": + return personIncome == incomeThreshold; + case ">": + return personIncome > incomeThreshold; + case "<": + return personIncome < incomeThreshold; + default: + return false; + } + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof IncomeComparisonPredicate)) { + return false; + } + + IncomeComparisonPredicate otherIncomeComparisonPredicate = + (IncomeComparisonPredicate) other; + return incomeThreshold == otherIncomeComparisonPredicate.incomeThreshold + && incomeComparisonOperator.equals(otherIncomeComparisonPredicate.incomeComparisonOperator); + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .add("incomeThreshold", incomeThreshold) + .add("incomeComparisonOperator", incomeComparisonOperator) + .toString(); + } + + /** + * Ensures that the income threshold is positive. + * + * @param incomeThreshold The threshold to check. + * @throws IllegalArgumentException if {@code incomeThreshold} is not greater than 1. + */ + private void checkPositiveIncomeThreshold(int incomeThreshold) { + if (incomeThreshold < 0) { + throw new IllegalArgumentException("Income threshold cannot be less than 0."); + } + } +} + diff --git a/src/main/java/seedu/address/model/person/predicates/IncomeContainsSubstringPredicate.java b/src/main/java/seedu/address/model/person/predicates/IncomeContainsSubstringPredicate.java deleted file mode 100644 index 098e76836e0..00000000000 --- a/src/main/java/seedu/address/model/person/predicates/IncomeContainsSubstringPredicate.java +++ /dev/null @@ -1,40 +0,0 @@ -package seedu.address.model.person.predicates; - -import seedu.address.commons.util.StringUtil; -import seedu.address.commons.util.ToStringBuilder; -import seedu.address.model.person.Person; - -/** - * Tests that a {@code Person}'s {@code Income} contains a specified substring. - */ -public class IncomeContainsSubstringPredicate extends ContainsSubstringPredicate { - public IncomeContainsSubstringPredicate(String substring) { - super(substring); - } - - @Override - public boolean test(Person person) { - return StringUtil.containsSubstringIgnoreCase(String.valueOf(person.getIncome().value), substring); - } - - @Override - public boolean equals(Object other) { - if (other == this) { - return true; - } - - // instanceof handles nulls - if (!(other instanceof IncomeContainsSubstringPredicate)) { - return false; - } - - IncomeContainsSubstringPredicate otherIncomeContainsSubstringPredicate = - (IncomeContainsSubstringPredicate) other; - return substring.equals(otherIncomeContainsSubstringPredicate.substring); - } - - @Override - public String toString() { - return new ToStringBuilder(this).add("substring", substring).toString(); - } -} diff --git a/src/main/java/seedu/address/model/util/IncomeComparisonOperator.java b/src/main/java/seedu/address/model/util/IncomeComparisonOperator.java new file mode 100644 index 00000000000..ed865bee5e8 --- /dev/null +++ b/src/main/java/seedu/address/model/util/IncomeComparisonOperator.java @@ -0,0 +1,61 @@ +package seedu.address.model.util; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +import java.util.Objects; + +/** + * Represents a comparison operator used in income comparisons. + * The operator can only be one of '=', '>', or '<'. + * Guarantees: comparisonOperator is validated upon creation. + */ +public class IncomeComparisonOperator { + public static final String MESSAGE_CONSTRAINTS = "Income comparison operators can only be '=', '>' or '<'"; + + public final String comparisonOperator; + + /** + * Constructs a {@code IncomeComparisonOperator}. + * + * @param comparisonOperator A comparisonOperator + */ + public IncomeComparisonOperator(String comparisonOperator) { + requireNonNull(comparisonOperator); + checkArgument(isValidComparisonOperator(comparisonOperator), MESSAGE_CONSTRAINTS); + this.comparisonOperator = comparisonOperator; + } + + /** + * Returns true if a given string is a valid comparison operator. + */ + public static boolean isValidComparisonOperator(String test) { + if (test.trim().isEmpty()) { + return false; + } + + return (test.equals("=") || test.equals(">") || test.equals("<")); + } + + @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } + if (other instanceof IncomeComparisonOperator) { + IncomeComparisonOperator otherIncomeComparisonOperator = (IncomeComparisonOperator) other; + return comparisonOperator.equals(otherIncomeComparisonOperator.comparisonOperator); + } + return false; + } + + @Override + public int hashCode() { + return Objects.hash(comparisonOperator); + } + + @Override + public String toString() { + return comparisonOperator; + } +} diff --git a/src/test/java/seedu/address/logic/parser/FilterCommandParserTest.java b/src/test/java/seedu/address/logic/parser/FilterCommandParserTest.java index 8e83a9f90e1..1f7d7608a1c 100644 --- a/src/test/java/seedu/address/logic/parser/FilterCommandParserTest.java +++ b/src/test/java/seedu/address/logic/parser/FilterCommandParserTest.java @@ -15,11 +15,13 @@ import seedu.address.model.person.predicates.AddressContainsSubstringPredicate; import seedu.address.model.person.predicates.CombinedPredicate; import seedu.address.model.person.predicates.EmailContainsSubstringPredicate; +import seedu.address.model.person.predicates.IncomeComparisonPredicate; import seedu.address.model.person.predicates.JobContainsSubstringPredicate; import seedu.address.model.person.predicates.NameContainsSubstringPredicate; import seedu.address.model.person.predicates.PhoneContainsSubstringPredicate; import seedu.address.model.person.predicates.RemarkContainsSubstringPredicate; import seedu.address.model.person.predicates.TierStartsWithSubstringPredicate; +import seedu.address.model.util.IncomeComparisonOperator; public class FilterCommandParserTest { @@ -58,13 +60,24 @@ public void parse_emailFlag_returnsEmailFilterCommand() { assertParseSuccess(parser, " e/ alice@hello.com", expectedFilterCommand); } + @Test + public void parse_incomeFlag_returnsIncomeFilterCommand() { + List> expectedPredicates = new ArrayList<>(); + expectedPredicates.add(new EmailContainsSubstringPredicate("alice@hello.com")); + FilterCommand expectedFilterCommand = new FilterCommand(new CombinedPredicate(expectedPredicates)); + + assertParseSuccess(parser, " e/ alice@hello.com", expectedFilterCommand); + } + @Test public void parse_jobFlag_returnsRemarkFilterCommand() { List> expectedPredicates = new ArrayList<>(); - expectedPredicates.add(new JobContainsSubstringPredicate("Software Engineer")); + IncomeComparisonOperator operator = new IncomeComparisonOperator(">"); + expectedPredicates.add(new IncomeComparisonPredicate(operator, 5000)); + FilterCommand expectedFilterCommand = new FilterCommand(new CombinedPredicate(expectedPredicates)); - assertParseSuccess(parser, " j/ Software Engineer", expectedFilterCommand); + assertParseSuccess(parser, " i/ >5000", expectedFilterCommand); } @Test @@ -112,13 +125,15 @@ public void parse_validMultipleArgs_returnsFilterCommand() { expectedPredicates.add(new EmailContainsSubstringPredicate("alice@example.com")); expectedPredicates.add(new AddressContainsSubstringPredicate("Block 123")); expectedPredicates.add(new JobContainsSubstringPredicate("Software Engineer")); + IncomeComparisonOperator operator = new IncomeComparisonOperator(">"); + expectedPredicates.add(new IncomeComparisonPredicate(operator, 5000)); expectedPredicates.add(new RemarkContainsSubstringPredicate("is a celebrity")); expectedPredicates.add(new TierStartsWithSubstringPredicate("GOLD")); FilterCommand expectedFilterCommand = new FilterCommand(new CombinedPredicate(expectedPredicates)); assertParseSuccess(parser, " n/ Alice p/ 91112222 e/ alice@example.com a/ Block 123 " - + "j/ Software Engineer r/ is a celebrity t/ gold", expectedFilterCommand); + + "j/ Software Engineer i/ >5000 r/ is a celebrity t/ gold", expectedFilterCommand); } @Test diff --git a/src/test/java/seedu/address/logic/parser/ParserUtilTest.java b/src/test/java/seedu/address/logic/parser/ParserUtilTest.java index 28f9d76d4ae..4e3eab7c986 100644 --- a/src/test/java/seedu/address/logic/parser/ParserUtilTest.java +++ b/src/test/java/seedu/address/logic/parser/ParserUtilTest.java @@ -10,23 +10,32 @@ import seedu.address.logic.parser.exceptions.ParseException; import seedu.address.model.person.Address; import seedu.address.model.person.Email; +import seedu.address.model.person.Income; import seedu.address.model.person.Name; import seedu.address.model.person.Phone; import seedu.address.model.tier.Tier; +import seedu.address.model.util.IncomeComparisonOperator; public class ParserUtilTest { private static final String INVALID_NAME = "R@chel"; private static final String INVALID_PHONE = "+651234"; private static final String INVALID_ADDRESS = " "; private static final String INVALID_EMAIL = "example.com"; + private static final String INVALID_INCOME = "one thousand"; private static final String INVALID_TAG = "#friend"; + private static final String INVALID_INCOME_COMPARISON_OPERATOR_1 = "=="; + private static final String INVALID_INCOME_COMPARISON_OPERATOR_2 = "!"; private static final String VALID_NAME = "Rachel Walker"; private static final String VALID_PHONE = "91234567"; private static final String VALID_ADDRESS = "123 Main Street #0505"; private static final String VALID_EMAIL = "rachel@example.com"; + private static final String VALID_INCOME = "1000"; private static final String VALID_TAG_1 = "BRONZE"; private static final String VALID_TAG_2 = "SILVER"; + private static final String VALID_INCOME_COMPARISON_OPERATOR_EQUAL = ">"; + private static final String VALID_INCOME_COMPARISON_OPERATOR_GREATER_THAN = ">"; + private static final String VALID_INCOME_COMPARISON_OPERATOR_LESS_THAN = "<"; private static final String WHITESPACE = " \t\r\n"; @@ -142,6 +151,29 @@ public void parseEmail_validValueWithWhitespace_returnsTrimmedEmail() throws Exc assertEquals(expectedEmail, ParserUtil.parseEmail(emailWithWhitespace)); } + @Test + public void parseIncome_null_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> ParserUtil.parseIncome((String) null)); + } + + @Test + public void parseIncome_invalidValue_throwsParseException() { + assertThrows(ParseException.class, () -> ParserUtil.parseIncome(INVALID_INCOME)); + } + + @Test + public void parseIncome_validValueWithoutWhitespace_returnsEmail() throws Exception { + Income expectedIncome = new Income(Integer.parseInt(VALID_INCOME)); + assertEquals(expectedIncome, ParserUtil.parseIncome(VALID_INCOME)); + } + + @Test + public void parseIncome_validValueWithWhitespace_returnsTrimmedIncome() throws Exception { + String incomeWithWhitespace = WHITESPACE + VALID_INCOME + WHITESPACE; + Income expectedIncome = new Income(Integer.parseInt(VALID_INCOME)); + assertEquals(expectedIncome, ParserUtil.parseIncome(incomeWithWhitespace)); + } + @Test public void parseTier_null_throwsNullPointerException() { assertThrows(NullPointerException.class, () -> ParserUtil.parseTier(null)); @@ -165,4 +197,53 @@ public void parseTier_validValueWithWhitespace_returnsTrimmedTag() throws Except assertEquals(expectedTier, ParserUtil.parseTier(tagWithWhitespace)); } + @Test + public void parseIncomeComparisonOperator_null_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> ParserUtil.parseIncomeComparisonOperator(null)); + } + + @Test + public void parseIncomeComparisonOperator_invalidValue_throwsParseException() { + assertThrows(ParseException.class, () -> + ParserUtil.parseIncomeComparisonOperator(INVALID_INCOME_COMPARISON_OPERATOR_1)); + assertThrows(ParseException.class, () -> + ParserUtil.parseIncomeComparisonOperator(INVALID_INCOME_COMPARISON_OPERATOR_2)); + } + + @Test + public void parseIncomeComparisonOperator_validValueWithoutWhitespace_returnsIncomeComparisonOperator() + throws Exception { + IncomeComparisonOperator equalOperator = new IncomeComparisonOperator( + VALID_INCOME_COMPARISON_OPERATOR_EQUAL); + IncomeComparisonOperator greaterThanOperator = new IncomeComparisonOperator( + VALID_INCOME_COMPARISON_OPERATOR_GREATER_THAN); + IncomeComparisonOperator lessThanOperator = new IncomeComparisonOperator( + VALID_INCOME_COMPARISON_OPERATOR_LESS_THAN); + + assertEquals(equalOperator, ParserUtil.parseIncomeComparisonOperator( + VALID_INCOME_COMPARISON_OPERATOR_EQUAL)); + assertEquals(greaterThanOperator, ParserUtil.parseIncomeComparisonOperator( + VALID_INCOME_COMPARISON_OPERATOR_GREATER_THAN)); + assertEquals(lessThanOperator, ParserUtil.parseIncomeComparisonOperator( + VALID_INCOME_COMPARISON_OPERATOR_LESS_THAN)); + } + + @Test + public void parseIncomeComparisonOperator_validValueWithWhitespace_returnsIncomeComparisonOperator() + throws Exception { + IncomeComparisonOperator equalOperator = new IncomeComparisonOperator( + VALID_INCOME_COMPARISON_OPERATOR_EQUAL); + IncomeComparisonOperator greaterThanOperator = new IncomeComparisonOperator( + VALID_INCOME_COMPARISON_OPERATOR_GREATER_THAN); + IncomeComparisonOperator lessThanOperator = new IncomeComparisonOperator( + VALID_INCOME_COMPARISON_OPERATOR_LESS_THAN); + + assertEquals(equalOperator, ParserUtil.parseIncomeComparisonOperator( + WHITESPACE + VALID_INCOME_COMPARISON_OPERATOR_EQUAL + WHITESPACE)); + assertEquals(greaterThanOperator, ParserUtil.parseIncomeComparisonOperator( + WHITESPACE + VALID_INCOME_COMPARISON_OPERATOR_GREATER_THAN + WHITESPACE)); + assertEquals(lessThanOperator, ParserUtil.parseIncomeComparisonOperator( + WHITESPACE + VALID_INCOME_COMPARISON_OPERATOR_LESS_THAN + WHITESPACE)); + } + } diff --git a/src/test/java/seedu/address/model/person/predicates/IncomeComparisonPredicateTest.java b/src/test/java/seedu/address/model/person/predicates/IncomeComparisonPredicateTest.java new file mode 100644 index 00000000000..a4028c79f26 --- /dev/null +++ b/src/test/java/seedu/address/model/person/predicates/IncomeComparisonPredicateTest.java @@ -0,0 +1,88 @@ +package seedu.address.model.person.predicates; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; + +import seedu.address.model.util.IncomeComparisonOperator; +import seedu.address.testutil.PersonBuilder; + +public class IncomeComparisonPredicateTest { + + @Test + public void equals() { + IncomeComparisonOperator operatorEqual = new IncomeComparisonOperator("="); + IncomeComparisonOperator operatorGreater = new IncomeComparisonOperator(">"); + + IncomeComparisonPredicate firstPredicate = new IncomeComparisonPredicate(operatorEqual, 5000); + IncomeComparisonPredicate secondPredicate = new IncomeComparisonPredicate(operatorGreater, 10000); + + // same object -> returns true + assertTrue(firstPredicate.equals(firstPredicate)); + + // same values -> returns true + IncomeComparisonPredicate firstPredicateCopy = new IncomeComparisonPredicate(operatorEqual, 5000); + assertTrue(firstPredicate.equals(firstPredicateCopy)); + + // different types -> returns false + assertFalse(firstPredicate.equals(1)); + + // null -> returns false + assertFalse(firstPredicate.equals(null)); + + // different predicate -> returns false + assertFalse(firstPredicate.equals(secondPredicate)); + } + + @Test + public void test_incomeEqualComparison_returnsTrue() { + IncomeComparisonPredicate predicate = new IncomeComparisonPredicate(new IncomeComparisonOperator("="), 5000); + assertTrue(predicate.test(new PersonBuilder().withIncome(5000).build())); + } + + @Test + public void test_incomeEqualComparison_returnsFalse() { + IncomeComparisonPredicate predicate = new IncomeComparisonPredicate(new IncomeComparisonOperator("="), 5000); + assertFalse(predicate.test(new PersonBuilder().withIncome(6000).build())); + assertFalse(predicate.test(new PersonBuilder().withIncome(4000).build())); + } + + @Test + public void test_incomeGreaterThanComparison_returnsTrue() { + IncomeComparisonPredicate predicate = new IncomeComparisonPredicate(new IncomeComparisonOperator(">"), 5000); + assertTrue(predicate.test(new PersonBuilder().withIncome(6000).build())); + } + + @Test + public void test_incomeGreaterThanComparison_returnsFalse() { + IncomeComparisonPredicate predicate = new IncomeComparisonPredicate(new IncomeComparisonOperator(">"), 5000); + assertFalse(predicate.test(new PersonBuilder().withIncome(4000).build())); + assertFalse(predicate.test(new PersonBuilder().withIncome(5000).build())); + } + + @Test + public void test_incomeLessThanComparison_returnsTrue() { + IncomeComparisonPredicate predicate = new IncomeComparisonPredicate(new IncomeComparisonOperator("<"), 5000); + assertTrue(predicate.test(new PersonBuilder().withIncome(4000).build())); + } + + @Test + public void test_incomeLessThanComparison_returnsFalse() { + // Income is not less than the threshold + IncomeComparisonPredicate predicate = new IncomeComparisonPredicate(new IncomeComparisonOperator("<"), 5000); + assertFalse(predicate.test(new PersonBuilder().withIncome(6000).build())); + assertFalse(predicate.test(new PersonBuilder().withIncome(5000).build())); + } + + @Test + public void toStringMethod() { + IncomeComparisonOperator operator = new IncomeComparisonOperator("="); + IncomeComparisonPredicate predicate = new IncomeComparisonPredicate(operator, 5000); + + String expected = IncomeComparisonPredicate.class.getCanonicalName() + + "{incomeThreshold=5000, incomeComparisonOperator==}"; + assertEquals(expected, predicate.toString()); + } +} diff --git a/src/test/java/seedu/address/model/util/IncomeComparisonOperatorTest.java b/src/test/java/seedu/address/model/util/IncomeComparisonOperatorTest.java new file mode 100644 index 00000000000..243920102a3 --- /dev/null +++ b/src/test/java/seedu/address/model/util/IncomeComparisonOperatorTest.java @@ -0,0 +1,89 @@ +package seedu.address.model.util; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.address.testutil.Assert.assertThrows; + +import org.junit.jupiter.api.Test; + +public class IncomeComparisonOperatorTest { + @Test + public void constructor_null_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> new IncomeComparisonOperator(null)); + } + + @Test + public void constructor_invalidOperator_throwsIllegalArgumentException() { + assertThrows(IllegalArgumentException.class, () -> new IncomeComparisonOperator("!")); + assertThrows(IllegalArgumentException.class, () -> new IncomeComparisonOperator("==")); + assertThrows(IllegalArgumentException.class, () -> new IncomeComparisonOperator("")); + } + + @Test + public void constructor_validOperator_success() { + assertDoesNotThrow(() -> new IncomeComparisonOperator("=")); + assertDoesNotThrow(() -> new IncomeComparisonOperator(">")); + assertDoesNotThrow(() -> new IncomeComparisonOperator("<")); + } + + @Test + public void isValidComparisonOperator() { + // Test valid operators + assertTrue(IncomeComparisonOperator.isValidComparisonOperator("=")); + assertTrue(IncomeComparisonOperator.isValidComparisonOperator(">")); + assertTrue(IncomeComparisonOperator.isValidComparisonOperator("<")); + + // Test invalid operators + assertFalse(IncomeComparisonOperator.isValidComparisonOperator("!")); + assertFalse(IncomeComparisonOperator.isValidComparisonOperator("==")); + assertFalse(IncomeComparisonOperator.isValidComparisonOperator(" ")); + assertFalse(IncomeComparisonOperator.isValidComparisonOperator("")); + assertFalse(IncomeComparisonOperator.isValidComparisonOperator(">=")); + } + + @Test + public void equals_sameObject_returnsTrue() { + IncomeComparisonOperator operator = new IncomeComparisonOperator("="); + assertEquals(operator, operator); + } + + @Test + public void equals_differentObjectsSameValue_returnsTrue() { + IncomeComparisonOperator operator1 = new IncomeComparisonOperator(">"); + IncomeComparisonOperator operator2 = new IncomeComparisonOperator(">"); + assertEquals(operator1, operator2); + } + + @Test + public void equals_differentObjectsDifferentValue_returnsFalse() { + IncomeComparisonOperator operator1 = new IncomeComparisonOperator(">"); + IncomeComparisonOperator operator2 = new IncomeComparisonOperator("<"); + assertNotEquals(operator1, operator2); + } + + @Test + public void hashCode_sameValue_returnsSameHashCode() { + // Test that objects with the same value return the same hash code + IncomeComparisonOperator operator1 = new IncomeComparisonOperator("<"); + IncomeComparisonOperator operator2 = new IncomeComparisonOperator("<"); + assertEquals(operator1.hashCode(), operator2.hashCode()); + } + + @Test + public void hashCode_differentValues_returnsDifferentHashCodes() { + // Test that objects with different values return different hash codes + IncomeComparisonOperator operator1 = new IncomeComparisonOperator("="); + IncomeComparisonOperator operator2 = new IncomeComparisonOperator(">"); + assertNotEquals(operator1.hashCode(), operator2.hashCode()); + } + + @Test + public void toString_returnsCorrectString() { + // Test that the toString method returns the correct string representation + IncomeComparisonOperator operator = new IncomeComparisonOperator("="); + assertEquals("=", operator.toString()); + } +}