Skip to content

Commit

Permalink
Add ability to filter by card difficulty with list command (#271)
Browse files Browse the repository at this point in the history
* add performance tests

* Remove use of dummy Performance

* add card performance matching predicate

* edit list command to take in and filter by performance arguments

* add comments

* fix style

* fix style

* add listcommand tests

* update user guide
  • Loading branch information
tomforge authored Nov 12, 2018
1 parent 23f5d58 commit 3f26577
Show file tree
Hide file tree
Showing 11 changed files with 300 additions and 7 deletions.
17 changes: 17 additions & 0 deletions docs/UserGuide.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,23 @@ Format: `help`
Displays a list of all available decks. If inside a deck displays all cards in that deck. +
Format: `list`

==== Filter by performance
This feature allows you to filter the displayed cards by their difficulty.

Format: `list [difficulty] [more difficulties]...`

When inside a deck, the `list` command can be used to filter the displayed cards by their difficulty, by passing
in the desired difficulty as a keyword after the command.

For example:

`list hard` will display all cards with the `hard` difficulty

Multiple difficulties can be specified as well, in which case all cards with a difficulty matching one of those
specified will be displayed:

`list easy hard` will display all cards with either an `easy` or `hard` difficulty.


=== Navigating into a deck : `cd`
To enter a deck identified by the INDEX_OF_DECK in the visible deck list. +
Expand Down
17 changes: 17 additions & 0 deletions docs/team/tomforge.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,23 @@ image::classifyExample3.png[width="500"]
+
image::classifyExample4.png[width="500"]

==== Filter by performance
This feature allows you to filter the displayed cards by their difficulty.

Format: `list [difficulty] [more difficulties]...`

When inside a deck, the `list` command can be used to filter the displayed cards by their difficulty, by passing
in the desired difficulty as a keyword after the command.

For example:

`list hard` will display all cards with the `hard` difficulty

Multiple difficulties can be specified as well, in which case all cards with a difficulty matching one of those
specified will be displayed:

`list easy hard` will display all cards with either an `easy` or `hard` difficulty.


== Contributions to the Developer Guide

Expand Down
35 changes: 32 additions & 3 deletions src/main/java/seedu/address/logic/commands/ListCommand.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,14 @@
import static seedu.address.model.Model.PREDICATE_SHOW_ALL_CARDS;
import static seedu.address.model.Model.PREDICATE_SHOW_ALL_DECKS;

import java.util.function.Predicate;
import java.util.stream.Collectors;

import seedu.address.logic.CommandHistory;
import seedu.address.logic.commands.exceptions.CommandException;
import seedu.address.model.Model;
import seedu.address.model.deck.Card;
import seedu.address.model.deck.CardPerformanceMatchesPerformancesPredicate;


/**
Expand All @@ -18,9 +23,33 @@ public class ListCommand extends Command {
public static final String COMMAND_WORD = "list";

public static final String MESSAGE_SUCCESS_DECK = "Listed all decks";
public static final String MESSAGE_SUCCESS_CARD = "Listed all cards";
public static final String MESSAGE_SUCCESS_ALL_CARDS = "Listed all cards";
public static final String MESSAGE_SUCCESS_PERFORMANCE_CARDS = "Listed %1$s cards";
public static final String AUTOCOMPLETE_TEXT = COMMAND_WORD;

private Predicate<Card> cardPredicate;
private String messageSuccessCard;

/**
* Creates the default ListCommand, which lists all cards
*/
public ListCommand() {
this.cardPredicate = PREDICATE_SHOW_ALL_CARDS;
this.messageSuccessCard = MESSAGE_SUCCESS_ALL_CARDS;
}

/**
* Creates a ListCommand with the given performance matching predicate, which will filter by the performances
* defined in said predicate
*
* @param cardPerformancePredicate the performance matching predicate
*/
public ListCommand(CardPerformanceMatchesPerformancesPredicate cardPerformancePredicate) {
this.cardPredicate = cardPerformancePredicate;
String performanceString = cardPerformancePredicate.performancesAsStrings().stream().collect(Collectors
.joining(", ")).toLowerCase();
this.messageSuccessCard = String.format(MESSAGE_SUCCESS_PERFORMANCE_CARDS, performanceString);
}


@Override
Expand All @@ -31,8 +60,8 @@ public CommandResult execute(Model model, CommandHistory history) throws Command
}

if (model.isInsideDeck()) {
model.updateFilteredCardList(PREDICATE_SHOW_ALL_CARDS);
return new CommandResult(MESSAGE_SUCCESS_CARD);
model.updateFilteredCardList(cardPredicate);
return new CommandResult(messageSuccessCard);
} else {
model.updateFilteredDeckList(PREDICATE_SHOW_ALL_DECKS);
return new CommandResult(MESSAGE_SUCCESS_DECK);
Expand Down
32 changes: 32 additions & 0 deletions src/main/java/seedu/address/logic/parser/ListCommandParser.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package seedu.address.logic.parser;

import java.util.Arrays;
import java.util.Set;
import java.util.stream.Collectors;

import seedu.address.logic.commands.ListCommand;
import seedu.address.model.deck.CardPerformanceMatchesPerformancesPredicate;
import seedu.address.model.deck.Performance;

/**
* Parses input elements and creates a new ListCommand object
*/
public class ListCommandParser implements ParserInterface<ListCommand> {
/**
* Parses the given {@code String} of arguments in the context of the ListCommand
* and returns an ListCommand object for execution.
*/
public ListCommand parse(String args) {
String trimmedArgs = args.trim();
if (!trimmedArgs.isEmpty()) {
String[] keywords = trimmedArgs.split("\\s+");
Set<Performance> performanceSet =
Arrays.stream(keywords).filter(Performance::isValidPerformance)
.map(Performance::type).collect(Collectors.toSet());
if (!performanceSet.isEmpty()) {
return new ListCommand(new CardPerformanceMatchesPerformancesPredicate(performanceSet));
}
}
return new ListCommand();
}
}
2 changes: 1 addition & 1 deletion src/main/java/seedu/address/logic/parser/Parser.java
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ public Command parseCommand(String userInput) throws ParseException {
return new HelpCommand();

case ListCommand.COMMAND_WORD:
return new ListCommand();
return new ListCommandParser().parse(arguments);

case ExportDeckCommand.COMMAND_WORD:
return new ExportDeckCommandParser().parse(arguments);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package seedu.address.model.deck;

import java.util.List;
import java.util.Set;
import java.util.function.Predicate;
import java.util.stream.Collectors;

/**
* Tests that a {@code Card}'s {@code Performance} matches any of the Performances given.
*/
public class CardPerformanceMatchesPerformancesPredicate implements Predicate<Card> {
private final Set<Performance> performances;

public CardPerformanceMatchesPerformancesPredicate(Set<Performance> performances) {
this.performances = performances;
}

/**
* Returns the constituent performances as a list of strings
*
* @return
*/
public List<String> performancesAsStrings() {
return performances.stream().map(Performance::toString).collect(Collectors.toList());
}

@Override
public boolean test(Card card) {
return performances.contains(card.getPerformance());
}

@Override
public boolean equals(Object other) {
if (other == this) {
return true;
} else if (!(other instanceof CardPerformanceMatchesPerformancesPredicate)) {
return false;
}
CardPerformanceMatchesPerformancesPredicate predicate = (CardPerformanceMatchesPerformancesPredicate) other;
return performances.size() == predicate.performances.size()
&& performances.containsAll(predicate.performances);
}
}
2 changes: 1 addition & 1 deletion src/main/java/seedu/address/model/deck/Performance.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public static boolean isValidPerformance(String type) {
return false;
}
try {
Performance dummy = Performance.type(type);
Performance.type(type);
return true;
} catch (IllegalArgumentException e) {
return false;
Expand Down
58 changes: 56 additions & 2 deletions src/test/java/seedu/address/logic/commands/ListCommandTest.java
Original file line number Diff line number Diff line change
@@ -1,18 +1,33 @@
package seedu.address.logic.commands;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess;
import static seedu.address.logic.commands.CommandTestUtil.showDeckAtIndex;
import static seedu.address.logic.commands.ListCommand.MESSAGE_SUCCESS_PERFORMANCE_CARDS;
import static seedu.address.testutil.TypicalCards.CARD_EASY;
import static seedu.address.testutil.TypicalCards.CARD_HARD;
import static seedu.address.testutil.TypicalDecks.DECK_WITH_CARDS;
import static seedu.address.testutil.TypicalDecks.THERE;
import static seedu.address.testutil.TypicalDecks.getTypicalAnakin;
import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_DECK;

import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

import org.junit.Before;
import org.junit.Test;

import seedu.address.logic.CommandHistory;
import seedu.address.model.Model;
import seedu.address.model.ModelManager;
import seedu.address.model.UserPrefs;
import seedu.address.model.deck.Card;
import seedu.address.model.deck.CardPerformanceMatchesPerformancesPredicate;
import seedu.address.model.deck.Performance;

/**
* Contains integration tests (interaction with the Model) and unit tests for ListCommand.
Expand All @@ -37,7 +52,7 @@ public void execute_listIsNotFiltered_showsSameList() {
model.getIntoDeck(DECK_WITH_CARDS);
expectedModel.getIntoDeck(DECK_WITH_CARDS);
assertCommandSuccess(new ListCommand(), model, commandHistory,
ListCommand.MESSAGE_SUCCESS_CARD, expectedModel);
ListCommand.MESSAGE_SUCCESS_ALL_CARDS, expectedModel);
}

@Test
Expand All @@ -50,6 +65,45 @@ public void execute_listIsFiltered_showsEverything() {
expectedModel.getIntoDeck(DECK_WITH_CARDS);
//showCardAtIndexOfCurrentDeck(model, INDEX_FIRST_CARD);
assertCommandSuccess(new ListCommand(), model, commandHistory,
ListCommand.MESSAGE_SUCCESS_CARD, expectedModel);
ListCommand.MESSAGE_SUCCESS_ALL_CARDS, expectedModel);
}

@Test
public void execute_hard_showsHard() {
model.getIntoDeck(THERE);
expectedModel.getIntoDeck(THERE);
String expectedMessage = String.format(MESSAGE_SUCCESS_PERFORMANCE_CARDS, "hard");
CardPerformanceMatchesPerformancesPredicate hardPredicate = preparePerformancePredicate(Set.of(Performance
.HARD));
ListCommand command = new ListCommand(hardPredicate);
expectedModel.updateFilteredCardList(hardPredicate);
assertCommandSuccess(command, model, commandHistory, expectedMessage, expectedModel);
assertEquals(Collections.singletonList(CARD_HARD), model.getFilteredCardList());
}

@Test
public void execute_easyHard_showsEasyHard() {
model.getIntoDeck(THERE);
expectedModel.getIntoDeck(THERE);
CardPerformanceMatchesPerformancesPredicate easyHardPredicate = preparePerformancePredicate(Set.of(Performance
.HARD, Performance.EASY));
String displayPerformanceStrings = convertPerformanceStringsForDisplay(easyHardPredicate
.performancesAsStrings());
String expectedMessage = String.format(MESSAGE_SUCCESS_PERFORMANCE_CARDS, displayPerformanceStrings);
ListCommand command = new ListCommand(easyHardPredicate);
expectedModel.updateFilteredCardList(easyHardPredicate);
assertCommandSuccess(command, model, commandHistory, expectedMessage, expectedModel);
Set<Card> expectedCards = Set.of(CARD_EASY, CARD_HARD);
Set<Card> actualCards = new HashSet<>(model.getFilteredCardList());
assertEquals(expectedCards.size(), actualCards.size());
assertTrue(expectedCards.containsAll(actualCards));
}

private CardPerformanceMatchesPerformancesPredicate preparePerformancePredicate(Set<Performance> performances) {
return new CardPerformanceMatchesPerformancesPredicate(performances);
}

public String convertPerformanceStringsForDisplay(List<String> performanceStrings) {
return performanceStrings.stream().collect(Collectors.joining(", ")).toLowerCase();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package seedu.address.model.deck;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertTrue;

import java.util.Set;

import org.junit.Test;

import seedu.address.testutil.CardBuilder;

public class CardPerformanceMatchesPerformancesTest {
private Set<Performance> hardPerformanceSet = Set.of(Performance.HARD);
private Set<Performance> easyHardPerformancesSet = Set.of(Performance.HARD, Performance.EASY);
private Set<Performance> hardEasyPerformancesSet = Set.of(Performance.EASY, Performance.HARD);
private Set<Performance> allPerformancesSet = Set.of(Performance.values());
private CardPerformanceMatchesPerformancesPredicate hardPerformancesPredicate = new
CardPerformanceMatchesPerformancesPredicate(hardPerformanceSet);
private CardPerformanceMatchesPerformancesPredicate easyHardPerformancesPredicate = new
CardPerformanceMatchesPerformancesPredicate(easyHardPerformancesSet);
private CardPerformanceMatchesPerformancesPredicate hardEasyPerformancesPredicate = new
CardPerformanceMatchesPerformancesPredicate(hardEasyPerformancesSet);
private CardPerformanceMatchesPerformancesPredicate allPerformanecsPredicate = new
CardPerformanceMatchesPerformancesPredicate(allPerformancesSet);

private Card easyCard = new CardBuilder().withPerformance(Performance.EASY).build();
private Card normalCard = new CardBuilder().withPerformance(Performance.NORMAL).build();
private Card hardCard = new CardBuilder().withPerformance(Performance.HARD).build();

@Test
public void equals() {
assertEquals(easyHardPerformancesPredicate, hardEasyPerformancesPredicate);
assertEquals(allPerformanecsPredicate, allPerformanecsPredicate);
assertNotEquals(easyHardPerformancesPredicate, hardPerformancesPredicate);
}

@Test
public void matchingPerformance_returnsTrue() {
assertTrue(hardPerformancesPredicate.test(hardCard));
assertTrue(easyHardPerformancesPredicate.test(easyCard));
assertTrue(easyHardPerformancesPredicate.test(hardCard));
assertTrue(allPerformanecsPredicate.test(easyCard));
assertTrue(allPerformanecsPredicate.test(normalCard));
assertTrue(allPerformanecsPredicate.test(hardCard));
}

@Test
public void notMatchingPerformance_returnsFalse() {
assertFalse(hardPerformancesPredicate.test(normalCard));
assertFalse(easyHardPerformancesPredicate.test(normalCard));
}
}
41 changes: 41 additions & 0 deletions src/test/java/seedu/address/model/deck/PerformanceTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package seedu.address.model.deck;

import static org.junit.Assert.assertEquals;

import org.junit.Test;

import seedu.address.testutil.Assert;

public class PerformanceTest {
private static final String INVALID_PERFORMANCE_STRING = "tough";
private static final Performance VALID_PERFORMANCE = Performance.HARD;
private static final String VALID_PERFORMANCE_STRING = "hard";
private static final String VALID_PERFORMANCE_STRING_MIXED_CASE = "hArD";

@Test
public void type_validPerformanceToString_returnsPerformance() {
Performance expectedPerformance = Performance.HARD;
Performance actualPerformance = Performance.type(expectedPerformance.toString());
assertEquals(expectedPerformance, actualPerformance);
}

@Test
public void type_validPerformanceString_returnsPerformance() {
assertEquals(VALID_PERFORMANCE, Performance.type(VALID_PERFORMANCE_STRING));
}

@Test
public void type_validPerformanceStringMixedCase_returnsPerformance() {
assertEquals(VALID_PERFORMANCE, Performance.type(VALID_PERFORMANCE_STRING_MIXED_CASE));
}

@Test
public void type_invalidPerformanceString_throwsIllegalArgumentException() {
Assert.assertThrows(IllegalArgumentException.class, () -> Performance.type(INVALID_PERFORMANCE_STRING));
}

@Test
public void type_nullString_throwsNullPointerException() {
Assert.assertThrows(NullPointerException.class, () -> Performance.type(null));
}
}
Loading

0 comments on commit 3f26577

Please sign in to comment.