diff --git a/pom.xml b/pom.xml
index 43ff4d1..3d5d903 100644
--- a/pom.xml
+++ b/pom.xml
@@ -1,5 +1,5 @@
-
4.0.0
@@ -78,11 +78,6 @@
com.h2database
h2
-
- org.springframework.boot
- spring-boot-starter-test
- test
-
org.flywaydb
@@ -101,6 +96,38 @@
sonar-maven-plugin
3.9.1.2184
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ 3.0.3
+ test
+
+
+
+ org.testfx
+ testfx-core
+ 4.0.16-alpha
+ test
+
+
+
+ org.testfx
+ testfx-junit5
+ 4.0.16-alpha
+ test
+
+
+
+ org.testfx
+ openjfx-monocle
+ jdk-11+26
+ test
+
+
+
org.hamcrest
hamcrest-library
@@ -172,6 +199,20 @@
false
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+ 3.0.0-M9
+
+
+
+ glass
+ Monocle
+ Headless
+ true
+
+
+
-
\ No newline at end of file
+
diff --git a/src/main/java/de/doubleslash/keeptask/common/Resources.java b/src/main/java/de/doubleslash/keeptask/common/Resources.java
index 5928114..609c894 100644
--- a/src/main/java/de/doubleslash/keeptask/common/Resources.java
+++ b/src/main/java/de/doubleslash/keeptask/common/Resources.java
@@ -24,6 +24,10 @@ private Resources() {
throw new IllegalStateException("Utility class");
}
+ public static URL getResource(final RESOURCE resource) {
+ return Resources.class.getResource(resource.getResourceLocation());
+ }
+
public enum RESOURCE {
/**
* FONTS
@@ -35,16 +39,16 @@ public enum RESOURCE {
* LAYOUTS
**/
// main
- FXML_VIEW_LAYOUT("/layouts/ViewLayout.fxml"),
+ FXML_VIEW_LAYOUT("/layouts/MainWindowLayout.fxml"),
FXML_EDIT_WORKITEM_LAYOUT("/layouts/EditWorkItemDialog.fxml"),
FXML_FILTER_LAYOUT("/layouts/FiltersLayout.fxml"),
+ FXML_SORTING_LAYOUT("/layouts/SortingLayout.fxml"),
SVG_TRASH_ICON("/svgs/trash-can.svg"),
SVG_PENCIL_ICON("/svgs/pencil.svg"),
- ICON_MAIN("/icons/icon.png")
- ;
+ ICON_MAIN("/icons/icon.png");
String resourceLocation;
@@ -56,8 +60,4 @@ public String getResourceLocation() {
return resourceLocation;
}
}
-
- public static URL getResource(final RESOURCE resource) {
- return Resources.class.getResource(resource.getResourceLocation());
- }
}
diff --git a/src/main/java/de/doubleslash/keeptask/controller/Controller.java b/src/main/java/de/doubleslash/keeptask/controller/Controller.java
index fa966ae..4c56084 100644
--- a/src/main/java/de/doubleslash/keeptask/controller/Controller.java
+++ b/src/main/java/de/doubleslash/keeptask/controller/Controller.java
@@ -16,12 +16,7 @@
package de.doubleslash.keeptask.controller;
-import java.time.LocalDateTime;
-import java.util.List;
-import java.util.function.Predicate;
-
-import javax.annotation.PreDestroy;
-
+import de.doubleslash.keeptask.model.Model;
import de.doubleslash.keeptask.model.WorkItem;
import de.doubleslash.keeptask.model.repos.WorkItemRepository;
import org.slf4j.Logger;
@@ -29,7 +24,9 @@
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
-import de.doubleslash.keeptask.model.Model;
+import javax.annotation.PreDestroy;
+import java.time.LocalDateTime;
+import java.util.List;
@Service
public class Controller {
@@ -95,11 +92,6 @@ public void shutdown() {
LOG.info("Controller shutdown");
}
- public void setFilterPredicate(Predicate filterPredicate) {
- LOG.debug("Filters were changed");
- model.getWorkFilteredList().setPredicate(filterPredicate);
- }
-
public void setLatestSelectedProject(String projectName) {
model.setLatestSelectedProject(projectName);
}
diff --git a/src/main/java/de/doubleslash/keeptask/controller/SortingController.java b/src/main/java/de/doubleslash/keeptask/controller/SortingController.java
new file mode 100644
index 0000000..0071608
--- /dev/null
+++ b/src/main/java/de/doubleslash/keeptask/controller/SortingController.java
@@ -0,0 +1,93 @@
+package de.doubleslash.keeptask.controller;
+
+import de.doubleslash.keeptask.model.Sorting.DueDate;
+import de.doubleslash.keeptask.model.Sorting.Priority;
+import de.doubleslash.keeptask.model.Sorting.SortingCriteria;
+import de.doubleslash.keeptask.model.Sorting.SortingDirection;
+import de.doubleslash.keeptask.model.WorkItem;
+import javafx.collections.FXCollections;
+import javafx.collections.ListChangeListener;
+import javafx.collections.ObservableList;
+import javafx.collections.transformation.SortedList;
+
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.List;
+import java.util.stream.Collectors;
+
+public class SortingController {
+ final ObservableList selectedSortingCriteriaList = FXCollections.observableArrayList();
+ private List possibleSortingCriteriaList;
+ private SortedList sortedWorkItems;
+
+ public SortingController() {
+ possibleSortingCriteriaList = Arrays.asList(
+ new Priority(),
+ new DueDate()
+ );
+ selectedSortingCriteriaList.addListener((ListChangeListener super SortingCriteria>) change -> updateComparator());
+ }
+
+ private Comparator getSortingComparator() {
+ Comparator comparator = null;
+ SortingCriteria sortingCriteria;
+
+ if (selectedSortingCriteriaList.size() > 0) {
+ sortingCriteria = selectedSortingCriteriaList.get(0);
+ comparator = Comparator.comparing(sortingCriteria.getOrderFunction(), Comparator.nullsLast(getComparatorBySortingDirection(sortingCriteria.getSortingDirection())));
+ }
+
+ for (int i = 1; i < selectedSortingCriteriaList.size(); i++) {
+ sortingCriteria = selectedSortingCriteriaList.get(i);
+ comparator = comparator.thenComparing(sortingCriteria.getOrderFunction(), Comparator.nullsLast(getComparatorBySortingDirection(sortingCriteria.getSortingDirection())));
+ }
+ return comparator;
+ }
+
+ private Comparator getComparatorBySortingDirection(final SortingDirection sortingDirection) {
+ Comparator comparator = null;
+
+ switch (sortingDirection) {
+ case Ascending:
+ comparator = Comparator.naturalOrder();
+ break;
+
+ case Descending:
+ comparator = Comparator.reverseOrder();
+ break;
+
+ default:
+ throw new RuntimeException("The selected sorting direction could not be mapped to a comparator.");
+ }
+
+ return comparator;
+ }
+
+ private void updateComparator() {
+ Comparator comparator = getSortingComparator();
+ sortedWorkItems.setComparator(comparator);
+ }
+
+ public void setWorkItemsToSort(ObservableList workItemsToSort) {
+ sortedWorkItems = new SortedList<>(workItemsToSort);
+ updateComparator();
+ }
+
+ public SortedList getSortedWorkItems() {
+ return sortedWorkItems;
+ }
+
+ public void addSortingCriteriaByString(String sortingCriteriaString) {
+ SortingCriteria foundSortingCriteria = possibleSortingCriteriaList.stream().filter(sortingCriteria -> sortingCriteria.getName() == sortingCriteriaString).findFirst().get();
+ selectedSortingCriteriaList.add(foundSortingCriteria);
+ }
+
+ public void removeSortingCriteriaByString(String sortingCriteriaString) {
+ SortingCriteria foundSortingCriteria = possibleSortingCriteriaList.stream().filter(sortingCriteria -> sortingCriteria.getName() == sortingCriteriaString).findFirst().get();
+ selectedSortingCriteriaList.remove(foundSortingCriteria);
+ }
+
+ public List getSortingCriterias() {
+ return possibleSortingCriteriaList.stream().map(SortingCriteria::getName).collect(Collectors.toList());
+ }
+}
diff --git a/src/main/java/de/doubleslash/keeptask/model/Model.java b/src/main/java/de/doubleslash/keeptask/model/Model.java
index 584986f..5d9ab01 100644
--- a/src/main/java/de/doubleslash/keeptask/model/Model.java
+++ b/src/main/java/de/doubleslash/keeptask/model/Model.java
@@ -16,35 +16,26 @@
package de.doubleslash.keeptask.model;
-import de.doubleslash.keeptask.model.repos.WorkItemRepository;
-import javafx.beans.InvalidationListener;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
-import javafx.beans.value.ChangeListener;
-import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
-import javafx.collections.transformation.FilteredList;
+import javafx.scene.paint.Color;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
-import javafx.scene.paint.Color;
-
import java.util.List;
@Component
public class Model {
-
public static final Color ORIGINAL_DEFAULT_BACKGROUND_COLOR = Color.WHITE;
public final ObjectProperty defaultBackgroundColor = new SimpleObjectProperty<>(
ORIGINAL_DEFAULT_BACKGROUND_COLOR);
private ObservableList workItems = FXCollections.observableArrayList();
- private FilteredList workFilteredItems = new FilteredList(workItems);
-
private StringProperty latestSelectedProject = new SimpleStringProperty();
@Autowired
@@ -52,21 +43,15 @@ public Model() {
super();
}
- public void setWorkItems(List workItems) {
- this.workItems.clear();
- this.workItems.addAll(workItems);
- }
-
public ObservableList getWorkItems() {
return FXCollections.unmodifiableObservableList(workItems);
}
- public ObservableList getWorkFilteredItems() {
- return FXCollections.unmodifiableObservableList(workFilteredItems);
+ public void setWorkItems(List workItems) {
+ this.workItems.clear();
+ this.workItems.addAll(workItems);
}
- public FilteredList getWorkFilteredList(){return workFilteredItems;}
-
public StringProperty latestSelectedProjectProperty() {
return latestSelectedProject;
}
diff --git a/src/main/java/de/doubleslash/keeptask/model/Sorting/DueDate.java b/src/main/java/de/doubleslash/keeptask/model/Sorting/DueDate.java
new file mode 100644
index 0000000..36600b7
--- /dev/null
+++ b/src/main/java/de/doubleslash/keeptask/model/Sorting/DueDate.java
@@ -0,0 +1,17 @@
+package de.doubleslash.keeptask.model.Sorting;
+
+import de.doubleslash.keeptask.model.WorkItem;
+
+import java.util.function.Function;
+
+public class DueDate extends SortingCriteria {
+ public DueDate() {
+ name = "Due Date";
+ sortingDirection = SortingDirection.Ascending;
+ }
+
+ @Override
+ public Function getOrderFunction() {
+ return WorkItem::getDueDateTime;
+ }
+}
diff --git a/src/main/java/de/doubleslash/keeptask/model/Sorting/Priority.java b/src/main/java/de/doubleslash/keeptask/model/Sorting/Priority.java
new file mode 100644
index 0000000..6358f3d
--- /dev/null
+++ b/src/main/java/de/doubleslash/keeptask/model/Sorting/Priority.java
@@ -0,0 +1,17 @@
+package de.doubleslash.keeptask.model.Sorting;
+
+import de.doubleslash.keeptask.model.WorkItem;
+
+import java.util.function.Function;
+
+public class Priority extends SortingCriteria {
+ public Priority() {
+ name = "Priority";
+ sortingDirection = SortingDirection.Descending;
+ }
+
+ @Override
+ public Function getOrderFunction() {
+ return WorkItem::getPriority;
+ }
+}
diff --git a/src/main/java/de/doubleslash/keeptask/model/Sorting/SortingCriteria.java b/src/main/java/de/doubleslash/keeptask/model/Sorting/SortingCriteria.java
new file mode 100644
index 0000000..fee0e7f
--- /dev/null
+++ b/src/main/java/de/doubleslash/keeptask/model/Sorting/SortingCriteria.java
@@ -0,0 +1,24 @@
+package de.doubleslash.keeptask.model.Sorting;
+
+import de.doubleslash.keeptask.model.WorkItem;
+
+import java.util.function.Function;
+
+public abstract class SortingCriteria {
+ protected String name = null;
+ protected SortingDirection sortingDirection = SortingDirection.Ascending;
+
+ public String getName() {
+ return name;
+ }
+
+ public SortingDirection getSortingDirection() {
+ return sortingDirection;
+ }
+
+ public void setSortingDirection(SortingDirection sortingDirection) {
+ this.sortingDirection = sortingDirection;
+ }
+
+ public abstract Function getOrderFunction();
+}
diff --git a/src/main/java/de/doubleslash/keeptask/model/Sorting/SortingDirection.java b/src/main/java/de/doubleslash/keeptask/model/Sorting/SortingDirection.java
new file mode 100644
index 0000000..a05d49f
--- /dev/null
+++ b/src/main/java/de/doubleslash/keeptask/model/Sorting/SortingDirection.java
@@ -0,0 +1,5 @@
+package de.doubleslash.keeptask.model.Sorting;
+
+public enum SortingDirection {
+ Ascending, Descending
+}
diff --git a/src/main/java/de/doubleslash/keeptask/model/WorkItem.java b/src/main/java/de/doubleslash/keeptask/model/WorkItem.java
index ce59c7b..8a13413 100644
--- a/src/main/java/de/doubleslash/keeptask/model/WorkItem.java
+++ b/src/main/java/de/doubleslash/keeptask/model/WorkItem.java
@@ -13,7 +13,9 @@ public class WorkItem {
private long id;
private String project;
- private String priority;
+
+ @Enumerated(EnumType.STRING)
+ private Priority priority;
private String todo;
private LocalDateTime createdDateTime;
private LocalDateTime dueDateTime;
@@ -21,38 +23,11 @@ public class WorkItem {
private boolean finished;
private String note;
- public void setProject(String project) {
- this.project = project;
- }
-
- public void setTodo(String todo) {
- this.todo = todo;
- }
-
- public void setCreatedDateTime(LocalDateTime createdDateTime) {
- this.createdDateTime = createdDateTime;
- }
-
- public void setDueDateTime(LocalDateTime dueDateTime) {
- this.dueDateTime = dueDateTime;
- }
-
- public void setCompletedDateTime(LocalDateTime completedDateTime) {
- this.completedDateTime = completedDateTime;
- }
-
- public void setFinished(boolean finished) {
- this.finished = finished;
- }
-
- public void setNote(String note) {
- this.note = note;
- }
-
- public WorkItem(){
+ public WorkItem() {
// needed for hibernate
}
- public WorkItem(String project, String priority, String todo, LocalDateTime createdDateTime, LocalDateTime dueDateTime, LocalDateTime completedDateTime, boolean finished, String note) {
+
+ public WorkItem(String project, Priority priority, String todo, LocalDateTime createdDateTime, LocalDateTime dueDateTime, LocalDateTime completedDateTime, boolean finished, String note) {
this.project = project;
this.priority = priority;
this.todo = todo;
@@ -67,42 +42,75 @@ public long getId() {
return id;
}
+ public void setId(long id) {
+ this.id = id;
+ }
+
public String getProject() {
return project;
}
+ public void setProject(String project) {
+ this.project = project;
+ }
+
public String getTodo() {
return todo;
}
+ public void setTodo(String todo) {
+ this.todo = todo;
+ }
+
public LocalDateTime getCreatedDateTime() {
return createdDateTime;
}
+ public void setCreatedDateTime(LocalDateTime createdDateTime) {
+ this.createdDateTime = createdDateTime;
+ }
+
public LocalDateTime getDueDateTime() {
return dueDateTime;
}
+ public void setDueDateTime(LocalDateTime dueDateTime) {
+ this.dueDateTime = dueDateTime;
+ }
+
public LocalDateTime getCompletedDateTime() {
return completedDateTime;
}
+ public void setCompletedDateTime(LocalDateTime completedDateTime) {
+ this.completedDateTime = completedDateTime;
+ }
+
public boolean isFinished() {
return finished;
}
+ public void setFinished(boolean finished) {
+ this.finished = finished;
+ }
+
public String getNote() {
return note;
}
- public String getPriority() {
+
+ public void setNote(String note) {
+ this.note = note;
+ }
+
+ public Priority getPriority() {
return priority;
}
- public void setPriority(String priority) {
+ public void setPriority(Priority priority) {
this.priority = priority;
}
- public void setId(long id) {
- this.id = id;
+ public enum Priority {
+ Low, Medium, High
}
}
diff --git a/src/main/java/de/doubleslash/keeptask/model/WorkItemBuilder.java b/src/main/java/de/doubleslash/keeptask/model/WorkItemBuilder.java
new file mode 100644
index 0000000..4d2e614
--- /dev/null
+++ b/src/main/java/de/doubleslash/keeptask/model/WorkItemBuilder.java
@@ -0,0 +1,58 @@
+package de.doubleslash.keeptask.model;
+
+import java.time.LocalDateTime;
+
+public class WorkItemBuilder {
+ private String project;
+ private WorkItem.Priority priority;
+ private String todo;
+ private LocalDateTime createdDateTime;
+ private LocalDateTime dueDateTime;
+ private LocalDateTime completedDateTime;
+ private boolean finished;
+ private String note;
+
+ public WorkItemBuilder setProject(String project) {
+ this.project = project;
+ return this;
+ }
+
+ public WorkItemBuilder setPriority(WorkItem.Priority priority) {
+ this.priority = priority;
+ return this;
+ }
+
+ public WorkItemBuilder setTodo(String todo) {
+ this.todo = todo;
+ return this;
+ }
+
+ public WorkItemBuilder setCreatedDateTime(LocalDateTime createdDateTime) {
+ this.createdDateTime = createdDateTime;
+ return this;
+ }
+
+ public WorkItemBuilder setDueDateTime(LocalDateTime dueDateTime) {
+ this.dueDateTime = dueDateTime;
+ return this;
+ }
+
+ public WorkItemBuilder setCompletedDateTime(LocalDateTime completedDateTime) {
+ this.completedDateTime = completedDateTime;
+ return this;
+ }
+
+ public WorkItemBuilder setFinished(boolean finished) {
+ this.finished = finished;
+ return this;
+ }
+
+ public WorkItemBuilder setNote(String note) {
+ this.note = note;
+ return this;
+ }
+
+ public WorkItem createWorkItem() {
+ return new WorkItem(project, priority, todo, createdDateTime, dueDateTime, completedDateTime, finished, note);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/de/doubleslash/keeptask/view/EditWorkItemController.java b/src/main/java/de/doubleslash/keeptask/view/EditWorkItemController.java
index 7c408ae..6824124 100644
--- a/src/main/java/de/doubleslash/keeptask/view/EditWorkItemController.java
+++ b/src/main/java/de/doubleslash/keeptask/view/EditWorkItemController.java
@@ -49,7 +49,7 @@ public void initializeWith(final WorkItem workItem) {
todoTextInput.setText(workItem.getTodo());
if (workItem.getDueDateTime() != null)
dueDateDatePicker.setValue(workItem.getDueDateTime().toLocalDate());
- priorityTextInput.setText(workItem.getPriority());
+ priorityTextInput.setText(workItem.getPriority().toString());
projectTextInput.setText(workItem.getProject());
if (workItem.getCreatedDateTime() != null)
createdDateDatePicker.setValue(workItem.getCreatedDateTime().toLocalDate());
@@ -64,7 +64,7 @@ public WorkItem getWorkItemFromUserInput() {
workItem.setTodo(todoTextInput.getText());
if (dueDateDatePicker.getValue() != null)
workItem.setDueDateTime(dueDateDatePicker.getValue().atStartOfDay());
- workItem.setPriority(priorityTextInput.getText());
+ workItem.setPriority(WorkItem.Priority.valueOf(priorityTextInput.getText()));
workItem.setProject(projectTextInput.getText());
if (createdDateDatePicker.getValue() != null)
workItem.setCreatedDateTime(createdDateDatePicker.getValue().atStartOfDay());
diff --git a/src/main/java/de/doubleslash/keeptask/view/FilterController.java b/src/main/java/de/doubleslash/keeptask/view/FilterController.java
index 2def301..36b4eb7 100644
--- a/src/main/java/de/doubleslash/keeptask/view/FilterController.java
+++ b/src/main/java/de/doubleslash/keeptask/view/FilterController.java
@@ -4,6 +4,7 @@
import de.doubleslash.keeptask.model.Model;
import de.doubleslash.keeptask.model.WorkItem;
import javafx.collections.ListChangeListener;
+import javafx.collections.transformation.FilteredList;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.fxml.FXML;
@@ -20,35 +21,28 @@
import java.util.Set;
import java.util.function.Predicate;
import java.util.stream.Collectors;
-import java.util.stream.Stream;
@Component
public class FilterController {
+ private final Model model;
+ private final Controller controller;
+ private final List> timeFilters = new ArrayList<>();
+ private final List projectNameFilters = new ArrayList<>();
@FXML
private TextField searchTextInput;
-
@FXML
private ToggleButton todayToggleButton;
-
@FXML
private ToggleButton tomorrowToggleButton;
-
@FXML
private ToggleButton expiredToggleButton;
-
@FXML
private CheckBox alsoCompletedCheckbox;
-
@FXML
private HBox projectFilterHbox;
- private final Model model;
- private final Controller controller;
-
- private final List> timeFilters = new ArrayList<>();
- private final List projectNameFilters = new ArrayList<>();
-
+ private FilteredList filteredWorkItems;
@Autowired
public FilterController(final Model model, final Controller controller) {
@@ -56,8 +50,14 @@ public FilterController(final Model model, final Controller controller) {
this.controller = controller;
}
+ public FilteredList getFilteredWorkItems() {
+ return filteredWorkItems;
+ }
+
@FXML
private void initialize() {
+ filteredWorkItems = new FilteredList<>(model.getWorkItems());
+
model.getWorkItems().addListener((ListChangeListener super WorkItem>) change -> {
updateProjectFilterButtons();
});
@@ -124,9 +124,10 @@ private void updateProjectFilterButtons() {
private void updateFilters() {
Predicate filterPredicate = generateFilterPredicate();
- controller.setFilterPredicate(filterPredicate);
+ filteredWorkItems.setPredicate(filterPredicate);
}
+
private Predicate generateFilterPredicate() {
Predicate filterPredicate = (workItem) -> {
boolean timeFilterMatches = timeFilters.isEmpty() ? true : timeFilters.stream().reduce(x -> false, Predicate::or).test(workItem);
diff --git a/src/main/java/de/doubleslash/keeptask/view/MainWindowController.java b/src/main/java/de/doubleslash/keeptask/view/MainWindowController.java
index b0cbb31..5fe392a 100644
--- a/src/main/java/de/doubleslash/keeptask/view/MainWindowController.java
+++ b/src/main/java/de/doubleslash/keeptask/view/MainWindowController.java
@@ -17,35 +17,40 @@
package de.doubleslash.keeptask.view;
import de.doubleslash.keeptask.common.*;
+import de.doubleslash.keeptask.controller.Controller;
+import de.doubleslash.keeptask.controller.SortingController;
import de.doubleslash.keeptask.exceptions.FXMLLoaderException;
+import de.doubleslash.keeptask.model.Model;
import de.doubleslash.keeptask.model.TodoPart;
import de.doubleslash.keeptask.model.WorkItem;
+import de.doubleslash.keeptask.model.WorkItemBuilder;
+import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
-import javafx.collections.transformation.SortedList;
+import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.Node;
import javafx.scene.control.*;
import javafx.scene.image.Image;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.HBox;
+import javafx.scene.layout.Pane;
import javafx.scene.layout.VBox;
+import javafx.scene.paint.Color;
+import javafx.stage.Stage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
-import de.doubleslash.keeptask.controller.Controller;
-import de.doubleslash.keeptask.model.Model;
-import javafx.fxml.FXML;
-import javafx.scene.layout.Pane;
-import javafx.scene.paint.Color;
-import javafx.stage.Stage;
-
import java.io.IOException;
import java.time.LocalDate;
import java.time.LocalDateTime;
-import java.util.*;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Optional;
+import java.util.stream.Collectors;
+
@Component
public class MainWindowController {
@@ -53,46 +58,35 @@ public class MainWindowController {
private final Model model;
private final Controller controller;
-
+ private FilterController filterController;
+ private SortingController sortingController;
private Point dragDelta = new Point(0, 0);
private Stage mainStage;
-
@FXML
private Pane pane;
-
@FXML
private Button minimizeButton;
-
@FXML
private Button closeButton;
-
-
@FXML
private VBox filterVBox;
-
// TODO extract new ToDo-section into own controller
@FXML
- private TextField prioTextInput;
-
+ private ComboBox prioComboBox;
@FXML
private TextField projectTextInput;
-
@FXML
private TextField todoTextInput;
-
@FXML
private DatePicker dueDatePicker;
-
@FXML
private Button addTodoButton;
-
+ @FXML
+ private VBox sortingVBox;
// TODO extract TODO ListView to own controller
@FXML
private VBox workItemVBox;
- SortedList sortedWorkItems;
-
-
@Autowired
public MainWindowController(final Model model, final Controller controller) {
this.model = model;
@@ -101,14 +95,9 @@ public MainWindowController(final Model model, final Controller controller) {
@FXML
private void initialize() {
- // TODO make sorting configurable
- sortedWorkItems = new SortedList<>(model.getWorkFilteredItems());
- Comparator comparing = Comparator.comparing(workItem -> workItem.getDueDateTime() != null ? workItem.getDueDateTime() : LocalDateTime.MIN);
- comparing = comparing.reversed();
- sortedWorkItems.setComparator(comparing);
-
-
+ prioComboBox.setItems(FXCollections.observableArrayList(Arrays.stream(WorkItem.Priority.values()).map(priority -> priority.toString()).collect(Collectors.toList())));
loadFiltersLayout();
+ loadSortingLayout();
closeButton.setOnAction(ae -> openConfirmationWindow());
minimizeButton.setOnAction(ae -> mainStage.setIconified(true));
@@ -135,14 +124,16 @@ private void initialize() {
if (dueDate != null) {
dueDateTime = dueDate.atStartOfDay();
}
- WorkItem newItem = new WorkItem(projectTextInput.getText(), prioTextInput.getText(), todoTextInput.getText(), LocalDateTime.now(), dueDateTime, null, false, "");
+ WorkItem newItem = new WorkItemBuilder().setProject(projectTextInput.getText()).setPriority(WorkItem.Priority.valueOf(prioComboBox.getSelectionModel().getSelectedItem())).setTodo(todoTextInput.getText()).setCreatedDateTime(LocalDateTime.now()).setDueDateTime(dueDateTime).setCompletedDateTime(null).setFinished(false).setNote("").createWorkItem();
controller.addWorkItem(newItem);
todoTextInput.clear();
dueDatePicker.setValue(null);
});
- model.getWorkFilteredItems().addListener((ListChangeListener super WorkItem>) change -> {
+ model.getWorkItems().addListener((ListChangeListener super WorkItem>) change -> {
+ if (!change.next()) return;
+
refreshTodos();
});
@@ -164,20 +155,42 @@ private void loadFiltersLayout() {
throw new RuntimeException(e);
}
this.filterVBox.getChildren().addAll(filterVBox.getChildren()); // TODO losses original vbox attributes
- FilterController filterController = loader.getController();
+ filterController = loader.getController();
+ }
+
+ private void loadSortingLayout() {
+ final FXMLLoader loader = FxmlLayout.createLoaderFor(Resources.RESOURCE.FXML_SORTING_LAYOUT);
+
+ final VBox sortingVBox;
+ try {
+ sortingVBox = loader.load();
+ } catch (IOException e) {
+ LOG.error("Could not load sorting layout", e);
+ throw new RuntimeException(e);
+ }
+ this.sortingVBox.getChildren().addAll(sortingVBox.getChildren());
+ SortingViewController sortingViewController = loader.getController();
+ sortingController = sortingViewController.getSortingController();
+ sortingController.setWorkItemsToSort(filterController.getFilteredWorkItems());
+ sortingController.getSortedWorkItems().addListener((ListChangeListener super WorkItem>) change -> {
+ if (!change.next()) return;
+
+ refreshTodos();
+ });
}
private void refreshTodos() {
ObservableList children = workItemVBox.getChildren();
children.clear();
- for (WorkItem workItem : sortedWorkItems) {
+ List sortedFilteredItems = sortingController.getSortedWorkItems();
+
+ for (WorkItem workItem : sortedFilteredItems) {
Node todoNode = createTodoNode(workItem);
children.add(todoNode);
}
- if (mainStage != null)
- mainStage.sizeToScene();
+ if (mainStage != null) mainStage.sizeToScene();
}
private Node createTodoNode(WorkItem workItem) {
@@ -194,13 +207,10 @@ private Node createTodoNode(WorkItem workItem) {
hbox2.setSpacing(10);
hbox2.disableProperty().bind(completedCheckBox.selectedProperty());
ObservableList children1 = hbox2.getChildren();
- Label prioLabel = new Label(workItem.getPriority());
- if (workItem.getPriority().equalsIgnoreCase("High"))
- prioLabel.setTextFill(Color.RED);
- if (workItem.getPriority().equalsIgnoreCase("Medium"))
- prioLabel.setTextFill(Color.ORANGE);
- if (!workItem.getPriority().isEmpty())
- children1.add(prioLabel);
+ Label prioLabel = new Label(workItem.getPriority().toString());
+ if (workItem.getPriority() == WorkItem.Priority.High) prioLabel.setTextFill(Color.RED);
+ if (workItem.getPriority() == WorkItem.Priority.Medium) prioLabel.setTextFill(Color.ORANGE);
+ if (!workItem.getPriority().toString().isEmpty()) children1.add(prioLabel);
Label projectLabel = new Label(workItem.getProject());
children1.add(projectLabel);
@@ -224,12 +234,10 @@ private Node createTodoNode(WorkItem workItem) {
children1.add(todoHbox);
Label dueDateTimeLabel = new Label("Due: " + workItem.getDueDateTime());
- if (workItem.getDueDateTime() != null)
- children1.add(dueDateTimeLabel);
+ if (workItem.getDueDateTime() != null) children1.add(dueDateTimeLabel);
Label noteLabel = new Label("Note: " + workItem.getNote());
- if (!workItem.getNote().isEmpty())
- children1.add(noteLabel);
+ if (!workItem.getNote().isEmpty()) children1.add(noteLabel);
Label completedDateTimeLabel = new Label("Completed: " + workItem.getCompletedDateTime());
if (workItem.isFinished()) // TODO this is only working on rerender
@@ -237,16 +245,14 @@ private Node createTodoNode(WorkItem workItem) {
children.add(hbox2);
- Button editButton = new Button("",
- SvgNodeProvider.getSvgNodeWithScale(Resources.RESOURCE.SVG_PENCIL_ICON, 0.03, 0.03));
+ Button editButton = new Button("", SvgNodeProvider.getSvgNodeWithScale(Resources.RESOURCE.SVG_PENCIL_ICON, 0.03, 0.03));
editButton.setMaxSize(20, 18);
editButton.setMinSize(20, 18);
editButton.setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
children.add(editButton);
editButton.setOnAction((actionEvent) -> editTodoClicked(workItem));
- Button deleteButton = new Button("",
- SvgNodeProvider.getSvgNodeWithScale(Resources.RESOURCE.SVG_TRASH_ICON, 0.03, 0.03));
+ Button deleteButton = new Button("", SvgNodeProvider.getSvgNodeWithScale(Resources.RESOURCE.SVG_TRASH_ICON, 0.03, 0.03));
deleteButton.setMaxSize(20, 18);
deleteButton.setMinSize(20, 18);
deleteButton.setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
@@ -305,8 +311,7 @@ private void editTodoClicked(WorkItem workItem) {
private void runUpdateMainBackgroundColor() {
Color color = model.defaultBackgroundColor.get();
- String style = StyleUtils.changeStyleAttribute(pane.getStyle(), "fx-background-color",
- "rgba(" + ColorHelper.colorToCssRgba(color) + ")");
+ String style = StyleUtils.changeStyleAttribute(pane.getStyle(), "fx-background-color", "rgba(" + ColorHelper.colorToCssRgba(color) + ")");
pane.setStyle(style);
}
@@ -330,5 +335,4 @@ public void setMainStage(Stage mainStage) {
this.mainStage = mainStage;
mainStage.sizeToScene();
}
-
}
diff --git a/src/main/java/de/doubleslash/keeptask/view/SortingViewController.java b/src/main/java/de/doubleslash/keeptask/view/SortingViewController.java
new file mode 100644
index 0000000..9e4032d
--- /dev/null
+++ b/src/main/java/de/doubleslash/keeptask/view/SortingViewController.java
@@ -0,0 +1,60 @@
+package de.doubleslash.keeptask.view;
+
+import de.doubleslash.keeptask.controller.SortingController;
+import javafx.collections.FXCollections;
+import javafx.fxml.FXML;
+import javafx.scene.control.Button;
+import javafx.scene.control.ComboBox;
+import javafx.scene.control.Tooltip;
+import javafx.scene.layout.HBox;
+import org.springframework.stereotype.Component;
+
+@Component
+public class SortingViewController {
+ private SortingController sortingController;
+ @FXML
+ private HBox sortingCriteriaHBox;
+ @FXML
+ private ComboBox addSortingCriteriaCbx;
+
+ SortingController getSortingController() {
+ return sortingController;
+ }
+
+ HBox getSortingCriteriaHBox() {
+ return sortingCriteriaHBox;
+ }
+
+ ComboBox getAddSortingCriteriaCbx() {
+ return addSortingCriteriaCbx;
+ }
+
+ @FXML
+ private void initialize() {
+ sortingController = new SortingController();
+
+ addSortingCriteriaCbx.setItems(FXCollections.observableArrayList(sortingController.getSortingCriterias()));
+ addSortingCriteriaCbx.getSelectionModel().selectedItemProperty().addListener((observable, oldValue, newValue) -> {
+ if (newValue == null) return;
+
+ sortingController.addSortingCriteriaByString(newValue.toString());
+ addSortingCriteriaButton(newValue.toString());
+ addSortingCriteriaCbx.getSelectionModel().clearSelection();
+ addSortingCriteriaCbx.getItems().remove(newValue);
+ });
+ }
+
+ private void addSortingCriteriaButton(String sortingCriteria) {
+ Button sortingCriteriaButton = new Button();
+ sortingCriteriaButton.setText(sortingCriteria);
+ Tooltip tooltip = new Tooltip();
+ tooltip.setText("Click to remove " + sortingCriteria + " sorting criteria");
+ sortingCriteriaButton.setTooltip(tooltip);
+ sortingCriteriaButton.setOnAction(event -> {
+ sortingController.removeSortingCriteriaByString(sortingCriteria);
+ addSortingCriteriaCbx.getItems().add(sortingCriteria);
+ sortingCriteriaHBox.getChildren().remove(sortingCriteriaButton);
+ });
+ sortingCriteriaHBox.getChildren().add(sortingCriteriaHBox.getChildren().size() - 1, sortingCriteriaButton);
+ }
+}
diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties
index 33323bd..f5a9bcb 100644
--- a/src/main/resources/application.properties
+++ b/src/main/resources/application.properties
@@ -21,5 +21,6 @@ spring.datasource.driver-class-name=org.h2.Driver
spring.flyway.baselineOnMigrate=true
spring.flyway.baselineVersion=0.0.0
+spring.flyway.validate-migration-naming=true
-spring.jpa.hibernate.ddl-auto=update
\ No newline at end of file
+spring.jpa.hibernate.ddl-auto=update
diff --git a/src/main/resources/db/migration/V2023_03_03_14_00__11-change_priority_data_type.sql b/src/main/resources/db/migration/V2023_03_03_14_00__11-change_priority_data_type.sql
new file mode 100644
index 0000000..8339087
--- /dev/null
+++ b/src/main/resources/db/migration/V2023_03_03_14_00__11-change_priority_data_type.sql
@@ -0,0 +1 @@
+UPDATE WORK_ITEM SET PRIORITY='Low' WHERE PRIORITY<>'Low' AND PRIORITY<>'Medium' AND PRIORITY<>'High';
diff --git a/src/main/resources/layouts/ViewLayout.fxml b/src/main/resources/layouts/MainWindowLayout.fxml
similarity index 59%
rename from src/main/resources/layouts/ViewLayout.fxml
rename to src/main/resources/layouts/MainWindowLayout.fxml
index 5d4bdaf..3cfb070 100644
--- a/src/main/resources/layouts/ViewLayout.fxml
+++ b/src/main/resources/layouts/MainWindowLayout.fxml
@@ -1,14 +1,9 @@
-
-
-
-
-
-
-
+
+
-
+
@@ -24,7 +19,7 @@
-
+
@@ -33,6 +28,7 @@
+
diff --git a/src/main/resources/layouts/SortingLayout.fxml b/src/main/resources/layouts/SortingLayout.fxml
new file mode 100644
index 0000000..99d0d37
--- /dev/null
+++ b/src/main/resources/layouts/SortingLayout.fxml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/test/java/de/doubleslash/keeptask/TestUtils.java b/src/test/java/de/doubleslash/keeptask/TestUtils.java
new file mode 100644
index 0000000..5211733
--- /dev/null
+++ b/src/test/java/de/doubleslash/keeptask/TestUtils.java
@@ -0,0 +1,14 @@
+package de.doubleslash.keeptask;
+
+import de.doubleslash.keeptask.model.WorkItem;
+
+import java.util.ArrayList;
+import java.util.Collections;
+
+public class TestUtils {
+ public static ArrayList getArrayListReverted(ArrayList arrayListToRevert) {
+ ArrayList revertedArrayList = (ArrayList) arrayListToRevert.clone();
+ Collections.reverse(revertedArrayList);
+ return revertedArrayList;
+ }
+}
diff --git a/src/test/java/de/doubleslash/keeptask/controller/SortingControllerTest.java b/src/test/java/de/doubleslash/keeptask/controller/SortingControllerTest.java
new file mode 100644
index 0000000..7e2abfe
--- /dev/null
+++ b/src/test/java/de/doubleslash/keeptask/controller/SortingControllerTest.java
@@ -0,0 +1,174 @@
+package de.doubleslash.keeptask.controller;
+
+import de.doubleslash.keeptask.model.Sorting.DueDate;
+import de.doubleslash.keeptask.model.Sorting.Priority;
+import de.doubleslash.keeptask.model.Sorting.SortingCriteria;
+import de.doubleslash.keeptask.model.Sorting.SortingDirection;
+import de.doubleslash.keeptask.model.WorkItem;
+import de.doubleslash.keeptask.model.WorkItemBuilder;
+import javafx.collections.FXCollections;
+import javafx.collections.ObservableList;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import java.time.LocalDateTime;
+import java.util.ArrayList;
+import java.util.List;
+
+import static de.doubleslash.keeptask.TestUtils.getArrayListReverted;
+import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
+
+class SortingControllerTest {
+ private SortingController sortingController;
+
+ @BeforeEach
+ void setup() {
+ sortingController = new SortingController();
+ }
+
+ @Test
+ void shouldSortWorkItemsDescendingByPriorityByDefaultWhenSettingPriorityAsSortingCriteria() {
+ ArrayList expectedSortedWorkItems = new ArrayList<>(List.of(
+ new WorkItemBuilder().setPriority(WorkItem.Priority.High).createWorkItem(),
+ new WorkItemBuilder().setPriority(WorkItem.Priority.Medium).createWorkItem(),
+ new WorkItemBuilder().setPriority(WorkItem.Priority.Low).createWorkItem()
+ ));
+
+ // GIVEN
+ ObservableList workItemsToBeSorted = FXCollections.observableArrayList(getArrayListReverted(expectedSortedWorkItems));
+ sortingController.setWorkItemsToSort(workItemsToBeSorted);
+
+ // WHEN
+ sortingController.selectedSortingCriteriaList.add(new Priority());
+
+ // THEN
+ assertThat(sortingController.getSortedWorkItems()).isEqualTo(expectedSortedWorkItems);
+ }
+
+ @Test
+ void shouldSortWorkItemsCorrectlyByDueDateWhenSettingDueDateAsSortingCriteria() {
+ ArrayList expectedSortedWorkItems = new ArrayList<>(List.of(
+ new WorkItemBuilder().setDueDateTime(LocalDateTime.now().plusDays(1)).createWorkItem(),
+ new WorkItemBuilder().setDueDateTime(LocalDateTime.now().plusDays(2)).createWorkItem(),
+ new WorkItemBuilder().setDueDateTime(LocalDateTime.now().plusDays(3)).createWorkItem()
+ ));
+
+ // GIVEN
+ ObservableList workItemsToBeSorted = FXCollections.observableArrayList(getArrayListReverted(expectedSortedWorkItems));
+ sortingController.setWorkItemsToSort(workItemsToBeSorted);
+
+ // WHEN
+ sortingController.selectedSortingCriteriaList.add(new DueDate());
+
+ // THEN
+ assertThat(sortingController.getSortedWorkItems()).isEqualTo(expectedSortedWorkItems);
+ }
+
+ @Test
+ void shouldPutWorkItemToBottomWhenUsedSortingCriteriaNotSetOnWorkItem() {
+ ArrayList expectedSortedWorkItems = new ArrayList<>(List.of(
+ new WorkItemBuilder().setPriority(WorkItem.Priority.High).createWorkItem(),
+ new WorkItemBuilder().setPriority(WorkItem.Priority.Medium).createWorkItem(),
+ new WorkItemBuilder().setPriority(null).createWorkItem()
+ ));
+
+ // GIVEN
+ ObservableList workItemsToBeSorted = FXCollections.observableArrayList(getArrayListReverted(expectedSortedWorkItems));
+ sortingController.setWorkItemsToSort(workItemsToBeSorted);
+
+ // WHEN
+ sortingController.selectedSortingCriteriaList.add(new Priority());
+
+ // THEN
+ assertThat(sortingController.getSortedWorkItems()).isEqualTo(expectedSortedWorkItems);
+ }
+
+ @Test
+ void shouldSortWorkItemsCorrectlyByPriorityAndDueDateWhenFirstSortingByPriorityAndThenByDueDate() {
+ ArrayList expectedSortedWorkItems = new ArrayList<>(List.of(
+ new WorkItemBuilder().setPriority(WorkItem.Priority.High).setDueDateTime(LocalDateTime.now().plusDays(1)).createWorkItem(),
+ new WorkItemBuilder().setPriority(WorkItem.Priority.High).setDueDateTime(LocalDateTime.now().plusDays(2)).createWorkItem(),
+ new WorkItemBuilder().setPriority(WorkItem.Priority.Medium).setDueDateTime(LocalDateTime.now().plusDays(1)).createWorkItem(),
+ new WorkItemBuilder().setPriority(WorkItem.Priority.Medium).setDueDateTime(LocalDateTime.now().plusDays(2)).createWorkItem(),
+ new WorkItemBuilder().setPriority(WorkItem.Priority.Low).setDueDateTime(LocalDateTime.now().plusDays(1)).createWorkItem(),
+ new WorkItemBuilder().setPriority(WorkItem.Priority.Low).setDueDateTime(LocalDateTime.now().plusDays(2)).createWorkItem()
+ ));
+
+ // GIVEN
+ ObservableList workItemsToBeSorted = FXCollections.observableArrayList(getArrayListReverted(expectedSortedWorkItems));
+ sortingController.setWorkItemsToSort(workItemsToBeSorted);
+
+ // WHEN
+ sortingController.selectedSortingCriteriaList.add(new Priority());
+ sortingController.selectedSortingCriteriaList.add(new DueDate());
+
+ // THEN
+ assertThat(sortingController.getSortedWorkItems()).isEqualTo(expectedSortedWorkItems);
+ }
+
+ @Test
+ void shouldSortWorkItemsCorrectlyByPriorityAndDueDateWhenFirstSortingByPriorityAndThenByDueDateIncludingNullValues() {
+ ArrayList expectedSortedWorkItems = new ArrayList<>(List.of(
+ new WorkItemBuilder().setPriority(WorkItem.Priority.High).setDueDateTime(LocalDateTime.now()).createWorkItem(),
+ new WorkItemBuilder().setPriority(WorkItem.Priority.High).setDueDateTime(LocalDateTime.now().plusDays(1)).createWorkItem(),
+ new WorkItemBuilder().setPriority(WorkItem.Priority.Medium).setDueDateTime(LocalDateTime.now()).createWorkItem(),
+ new WorkItemBuilder().setPriority(WorkItem.Priority.Medium).setDueDateTime(LocalDateTime.now().plusDays(1)).createWorkItem(),
+ new WorkItemBuilder().setPriority(WorkItem.Priority.Medium).setDueDateTime(null).createWorkItem(),
+ new WorkItemBuilder().setPriority(WorkItem.Priority.Low).setDueDateTime(LocalDateTime.now()).createWorkItem(),
+ new WorkItemBuilder().setPriority(WorkItem.Priority.Low).setDueDateTime(LocalDateTime.now().plusDays(1)).createWorkItem(),
+ new WorkItemBuilder().setPriority(null).setDueDateTime(LocalDateTime.now()).createWorkItem(),
+ new WorkItemBuilder().setPriority(null).setDueDateTime(null).createWorkItem()
+ ));
+
+ // GIVEN
+ ObservableList workItemsToBeSorted = FXCollections.observableArrayList(getArrayListReverted(expectedSortedWorkItems));
+ sortingController.setWorkItemsToSort(workItemsToBeSorted);
+
+ // WHEN
+ sortingController.selectedSortingCriteriaList.add(new Priority());
+ sortingController.selectedSortingCriteriaList.add(new DueDate());
+
+ // THEN
+ assertThat(sortingController.getSortedWorkItems()).isEqualTo(expectedSortedWorkItems);
+ }
+
+ @Test
+ void shouldSortWorkItemsDescendingByPriorityWhenSettingPriorityAsSortingCriteriaAndDescendingOrder() {
+ ArrayList expectedSortedWorkItems = new ArrayList<>(List.of(
+ new WorkItemBuilder().setPriority(WorkItem.Priority.High).createWorkItem(),
+ new WorkItemBuilder().setPriority(WorkItem.Priority.Medium).createWorkItem(),
+ new WorkItemBuilder().setPriority(WorkItem.Priority.Low).createWorkItem()
+ ));
+
+ // GIVEN
+ ObservableList workItemsToBeSorted = FXCollections.observableArrayList(getArrayListReverted(expectedSortedWorkItems));
+ sortingController.setWorkItemsToSort(workItemsToBeSorted);
+
+ // WHEN
+ sortingController.selectedSortingCriteriaList.add(new Priority());
+
+ // THEN
+ assertThat(sortingController.getSortedWorkItems()).isEqualTo(expectedSortedWorkItems);
+ }
+
+ @Test
+ void shouldSortWorkItemsAscendingByPriorityWhenSettingPriorityAsSortingCriteriaAndAscendingOrder() {
+ ArrayList expectedSortedWorkItems = new ArrayList<>(List.of(
+ new WorkItemBuilder().setPriority(WorkItem.Priority.Low).createWorkItem(),
+ new WorkItemBuilder().setPriority(WorkItem.Priority.Medium).createWorkItem(),
+ new WorkItemBuilder().setPriority(WorkItem.Priority.High).createWorkItem()
+ ));
+
+ // GIVEN
+ ObservableList workItemsToBeSorted = FXCollections.observableArrayList(getArrayListReverted(expectedSortedWorkItems));
+ sortingController.setWorkItemsToSort(workItemsToBeSorted);
+
+ // WHEN
+ SortingCriteria priority = new Priority();
+ priority.setSortingDirection(SortingDirection.Ascending);
+ sortingController.selectedSortingCriteriaList.add(priority);
+
+ // THEN
+ assertThat(sortingController.getSortedWorkItems()).isEqualTo(expectedSortedWorkItems);
+ }
+}
diff --git a/src/test/java/de/doubleslash/keeptask/view/SortingViewControllerTest.java b/src/test/java/de/doubleslash/keeptask/view/SortingViewControllerTest.java
new file mode 100644
index 0000000..d7c5229
--- /dev/null
+++ b/src/test/java/de/doubleslash/keeptask/view/SortingViewControllerTest.java
@@ -0,0 +1,61 @@
+package de.doubleslash.keeptask.view;
+
+
+import de.doubleslash.keeptask.common.Resources;
+import de.doubleslash.keeptask.model.WorkItem;
+import de.doubleslash.keeptask.model.WorkItemBuilder;
+import javafx.collections.FXCollections;
+import javafx.collections.ObservableList;
+import javafx.fxml.FXMLLoader;
+import javafx.scene.Node;
+import javafx.scene.control.Button;
+import javafx.stage.Stage;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.testfx.framework.junit5.ApplicationExtension;
+import org.testfx.framework.junit5.Start;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import static de.doubleslash.keeptask.TestUtils.getArrayListReverted;
+import static org.assertj.core.api.Assertions.assertThat;
+
+@ExtendWith(ApplicationExtension.class)
+class SortingViewControllerTest {
+ SortingViewController sortingViewController;
+
+ @Start
+ private void start(Stage stage) throws IOException {
+ final FXMLLoader loader = new FXMLLoader();
+ loader.setLocation(Resources.getResource(Resources.RESOURCE.FXML_SORTING_LAYOUT));
+ loader.load();
+ sortingViewController = loader.getController();
+ }
+
+ @Test
+ void shouldAddSortingCriteriaWhenSelectedViaComboBox() {
+ String expectedSelectedSortingCriteria = "Priority";
+ ArrayList expectedSortedWorkItems = new ArrayList<>(List.of(
+ new WorkItemBuilder().setPriority(WorkItem.Priority.High).createWorkItem(),
+ new WorkItemBuilder().setPriority(WorkItem.Priority.Medium).createWorkItem(),
+ new WorkItemBuilder().setPriority(WorkItem.Priority.Low).createWorkItem()
+ ));
+
+ // GIVEN
+ ObservableList workItemsToBeSorted = FXCollections.observableArrayList(getArrayListReverted(expectedSortedWorkItems));
+ sortingViewController.getSortingController().setWorkItemsToSort(workItemsToBeSorted);
+
+ // WHEN
+ sortingViewController.getAddSortingCriteriaCbx().getSelectionModel().select(expectedSelectedSortingCriteria);
+
+ // THEN
+ List buttonsList = sortingViewController.getSortingCriteriaHBox().getChildren().stream().filter(node -> node instanceof Button).collect(Collectors.toList());
+ assertThat(buttonsList).hasSize(1);
+ Button button = (Button) buttonsList.get(0);
+ assertThat(button.getText()).isEqualTo(expectedSelectedSortingCriteria);
+ assertThat(sortingViewController.getSortingController().getSortedWorkItems()).isEqualTo(expectedSortedWorkItems);
+ }
+}