diff --git a/README.md b/README.md
index 53ae0bb6..7919d453 100644
--- a/README.md
+++ b/README.md
@@ -115,6 +115,16 @@ This is generated automatically. The Java FX user interface allows you to edit i
## Recent changes
+### Version 1.0.1 (unreleased)
+
+See [Release](https://github.com/itsallcode/white-rabbit/releases/tag/v1.0.1) / [Milestone](https://github.com/itsallcode/white-rabbit/milestone/3?closed=1)
+
+* [#42](https://github.com/itsallcode/white-rabbit/pull/42): Bugfix: Keep edit focus for activities when table is updated every minute
+
+### Version 1.0.0
+
+See [Release](https://github.com/itsallcode/white-rabbit/releases/tag/v1.0.0) / [Milestone](https://github.com/itsallcode/white-rabbit/milestone/1?closed=1)
+
* [#40](https://github.com/itsallcode/white-rabbit/pull/40): Bugfix: Keep edit focus when table is updated every minute
* [#39](https://github.com/itsallcode/white-rabbit/pull/39): Publish WhiteRabbit using WebStart
* [#5](https://github.com/itsallcode/white-rabbit/issues/5): Add presets for adding interruptions
@@ -124,7 +134,7 @@ This is generated automatically. The Java FX user interface allows you to edit i
* [#29](https://github.com/itsallcode/white-rabbit/issues/29): Relaxed parsing of time and duration
* [#27](https://github.com/itsallcode/white-rabbit/issues/27): Delete begin, end and interruption when changing day type to "not working"
* [#26](https://github.com/itsallcode/white-rabbit/issues/26): Omit "activities" from json when list is empty
-* [#10](https://github.com/itsallcode/white-rabbit/issues/10): Facelift: Improved menu. Turned buttons an drop-down into toolbar. Turned OT (thanks to [redcatbear](https://github.com/redcatbear))
+* [#10](https://github.com/itsallcode/white-rabbit/issues/10): Facelift: Improved menu. Turned buttons and drop-down into toolbar. Turned OT (thanks to [redcatbear](https://github.com/redcatbear))
* [#6](https://github.com/itsallcode/white-rabbit/issues/6): Persist cell changes on focus loss
* Text UI is now deprecated, please use the new Java FX UI.
* Keep track of activities for time booking on multiple projects, See [project configuration](#project_config)
diff --git a/jfxui/build.gradle b/jfxui/build.gradle
index 2f85f90e..1b30d451 100644
--- a/jfxui/build.gradle
+++ b/jfxui/build.gradle
@@ -66,6 +66,9 @@ task uiTest(type: Test) {
useJUnitPlatform()
+ forkEvery 5
+ jvmArgs '-XX:+HeapDumpOnOutOfMemoryError'
+
if(!project.hasProperty("uiTestsHeadless") || project.property("uiTestsHeadless") != "false") {
systemProperty 'java.awt.headless', 'true'
systemProperty 'testfx.headless', 'true'
diff --git a/jfxui/src/main/java/org/itsallcode/whiterabbit/jfxui/JavaFxApp.java b/jfxui/src/main/java/org/itsallcode/whiterabbit/jfxui/JavaFxApp.java
index e299038e..92e56d80 100644
--- a/jfxui/src/main/java/org/itsallcode/whiterabbit/jfxui/JavaFxApp.java
+++ b/jfxui/src/main/java/org/itsallcode/whiterabbit/jfxui/JavaFxApp.java
@@ -321,10 +321,9 @@ private void openHomepage(ButtonType ignored)
private void createUi()
{
LOG.debug("Creating user interface");
- dayRecordTable = new DayRecordTable(locale, currentMonth, record -> {
- appService.store(record);
- appService.updateNow();
- }, appService.formatter());
+ dayRecordTable = new DayRecordTable(locale, currentMonth,
+ record -> appService.store(record),
+ appService.formatter());
activitiesTable = new ActivitiesTable(dayRecordTable.selectedDay(), record -> {
appService.store(record);
diff --git a/jfxui/src/main/java/org/itsallcode/whiterabbit/jfxui/table/activities/ActivitiesTable.java b/jfxui/src/main/java/org/itsallcode/whiterabbit/jfxui/table/activities/ActivitiesTable.java
index 17688197..00f7f44c 100644
--- a/jfxui/src/main/java/org/itsallcode/whiterabbit/jfxui/table/activities/ActivitiesTable.java
+++ b/jfxui/src/main/java/org/itsallcode/whiterabbit/jfxui/table/activities/ActivitiesTable.java
@@ -56,16 +56,43 @@ public ActivitiesTable(ReadOnlyProperty selectedDay, EditListener {
- activities.clear();
- if (day != null)
+ if (day == null || day.activities().isEmpty())
{
- final List selectedDayActivities = day.activities().getAll();
- LOG.trace("Day {} selected with {} activities", day.getDate(), selectedDayActivities.size());
- activities.addAll(ActivityPropertyAdapter.wrap(editListener, selectedDayActivities));
+ LOG.trace("No day selected or no activities: clear list of activities");
+ activities.clear();
+ return;
+ }
+ final List selectedDayActivities = day.activities().getAll();
+ LOG.trace("Day {} selected with {} activities", day.getDate(), selectedDayActivities.size());
+
+ removeSurplusRows(selectedDayActivities);
+ for (int i = 0; i < selectedDayActivities.size(); i++)
+ {
+ final Activity activity = selectedDayActivities.get(i);
+ if (activities.size() <= i)
+ {
+ LOG.trace("Add activity #{}: {}", i, activity);
+ activities.add(ActivityPropertyAdapter.wrap(editListener, activity));
+ }
+ else
+ {
+ LOG.trace("Update activity #{}: {}", i, activity);
+ activities.get(i).setActivity(activity);
+ }
}
});
}
+ private void removeSurplusRows(final List selectedDayActivities)
+ {
+ final int activitiesToRemove = Math.max(0, activities.size() - selectedDayActivities.size());
+ LOG.trace("Removing {} surplus rows of {}", activitiesToRemove, activities.size());
+ for (int i = 0; i < activitiesToRemove; i++)
+ {
+ activities.remove(activities.size() - 1);
+ }
+ }
+
public TableView initTable()
{
final TableView table = new TableView<>(activities);
diff --git a/jfxui/src/main/java/org/itsallcode/whiterabbit/jfxui/table/activities/ActivityPropertyAdapter.java b/jfxui/src/main/java/org/itsallcode/whiterabbit/jfxui/table/activities/ActivityPropertyAdapter.java
index 2da093ce..8fc4bcf4 100644
--- a/jfxui/src/main/java/org/itsallcode/whiterabbit/jfxui/table/activities/ActivityPropertyAdapter.java
+++ b/jfxui/src/main/java/org/itsallcode/whiterabbit/jfxui/table/activities/ActivityPropertyAdapter.java
@@ -36,6 +36,12 @@ private ActivityPropertyAdapter(EditListener editListener, Activity a
update();
}
+ public void setActivity(Activity activity)
+ {
+ super.setRecord(activity);
+ update();
+ }
+
public void update()
{
runUpdate(() -> {
@@ -44,10 +50,15 @@ public void update()
});
}
+ public static ActivityPropertyAdapter wrap(EditListener editListener, Activity activity)
+ {
+ return new ActivityPropertyAdapter(editListener, activity);
+ }
+
static List<@NonNull ActivityPropertyAdapter> wrap(EditListener editListener, List activities)
{
return activities.stream()
- .map(a -> new ActivityPropertyAdapter(editListener, a))
+ .map(a -> wrap(editListener, a))
.collect(toList());
}
}
diff --git a/jfxui/src/main/resources/log4j2.xml b/jfxui/src/main/resources/log4j2.xml
index 899801ca..9c3bf197 100644
--- a/jfxui/src/main/resources/log4j2.xml
+++ b/jfxui/src/main/resources/log4j2.xml
@@ -19,8 +19,8 @@
size="10 MB" />
-
+
diff --git a/jfxui/src/uiTest/java/org/itsallcode/whiterabbit/jfxui/ActivitiesTest.java b/jfxui/src/uiTest/java/org/itsallcode/whiterabbit/jfxui/ActivitiesTest.java
index 19041ffc..80bc1999 100644
--- a/jfxui/src/uiTest/java/org/itsallcode/whiterabbit/jfxui/ActivitiesTest.java
+++ b/jfxui/src/uiTest/java/org/itsallcode/whiterabbit/jfxui/ActivitiesTest.java
@@ -3,6 +3,7 @@
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertAll;
+import java.time.Duration;
import java.time.Instant;
import java.util.Locale;
@@ -10,6 +11,7 @@
import org.itsallcode.whiterabbit.jfxui.table.days.DayRecordPropertyAdapter;
import org.itsallcode.whiterabbit.jfxui.testutil.ActivitiesTableExpectedRow;
import org.itsallcode.whiterabbit.jfxui.testutil.ActivitiesTableExpectedRow.Builder;
+import org.itsallcode.whiterabbit.jfxui.testutil.model.ActivitiesTable;
import org.itsallcode.whiterabbit.jfxui.testutil.model.JavaFxTable;
import org.itsallcode.whiterabbit.logic.service.project.Project;
import org.junit.jupiter.api.Disabled;
@@ -21,7 +23,6 @@
import org.testfx.framework.junit5.Stop;
import javafx.scene.Node;
-import javafx.scene.control.Button;
import javafx.scene.input.KeyCode;
import javafx.stage.Stage;
@@ -37,14 +38,14 @@ class ActivitiesTest extends JavaFxAppUiTestBase
@Test
void addActivityButtonDisabledWhenNoDaySelected()
{
- assertThat(getAddActivityButton().isDisabled()).isTrue();
+ assertThat(app().activitiesTable().getAddActivityButton().isDisabled()).isTrue();
}
@Test
void addActivityButtonEnabledWhenDaySelected()
{
selectCurrentDay();
- assertThat(getAddActivityButton().isDisabled()).isFalse();
+ assertThat(app().activitiesTable().getAddActivityButton().isDisabled()).isFalse();
}
@Test
@@ -52,7 +53,7 @@ void activityTableEmptyByDefault()
{
selectCurrentDay();
time().tickMinute();
- lookupActivitiesTable().assertRowCount(0);
+ app().activitiesTable().table().assertRowCount(0);
}
@Test
@@ -60,9 +61,173 @@ void clickingAddButtonAddsActivity()
{
selectCurrentDay();
time().tickMinute();
- clickAddActivityButton();
+ final ActivitiesTable activities = app().activitiesTable();
- lookupActivitiesTable().assertRowCount(1);
+ activities.addActivity();
+
+ activities.table().assertRowCount(1);
+ }
+
+ @Disabled("Not implemented yet, see https://github.com/itsallcode/white-rabbit/issues/23")
+ @Test
+ void removeButtonDisabledWhenNoActivitySelected()
+ {
+ selectCurrentDay();
+ time().tickMinute();
+ final ActivitiesTable activities = app().activitiesTable();
+
+ activities.addActivity();
+
+ activities.table().assertRowCount(1);
+
+ assertThat(activities.getRemoveActivityButton().isDisable()).isTrue();
+ }
+
+ @Test
+ void clickRemoveButtonWhenNoActivitySelectedDoesNothing()
+ {
+ selectCurrentDay();
+ time().tickMinute();
+ final ActivitiesTable activities = app().activitiesTable();
+
+ activities.addActivity();
+
+ activities.table().assertRowCount(1);
+
+ activities.removeActivity();
+
+ activities.table().assertRowCount(1);
+ }
+
+ @Test
+ void clickRemoveButtonRemovesSelectedActivity()
+ {
+ selectCurrentDay();
+ time().tickMinute();
+ final ActivitiesTable activities = app().activitiesTable();
+
+ activities.addActivity();
+
+ activities.table().assertRowCount(1);
+
+ activities.table().clickRow(0);
+
+ activities.removeActivity();
+
+ activities.table().assertRowCount(0);
+ }
+
+ @Test
+ void addRemainderActivityWithValues()
+ {
+ time().tickMinute();
+ final ActivitiesTable activities = app().activitiesTable();
+
+ final Project project = new Project("p1", "Project 1", null);
+ activities.addRemainderActivity(project, "tst");
+
+ activities.table().assertContent(ActivitiesTableExpectedRow.defaultRow()
+ .withProject(project)
+ .withDuration(Duration.ZERO)
+ .withRemainder(true)
+ .withComment("tst")
+ .build());
+ }
+
+ @Test
+ void addFixedDurationActivityWithValues()
+ {
+ time().tickMinute();
+ final ActivitiesTable activities = app().activitiesTable();
+
+ final Project project = new Project("p1", "Project 1", null);
+ activities.addActivity(project, Duration.ofMinutes(5), "tst");
+
+ activities.table().assertContent(ActivitiesTableExpectedRow.defaultRow()
+ .withProject(project)
+ .withDuration(Duration.ofMinutes(5))
+ .withRemainder(false)
+ .withComment("tst")
+ .build());
+ }
+
+ @Test
+ void remainderDurationCalculated()
+ {
+ time().tickSeparateMinutes(11);
+
+ final ActivitiesTable activities = app().activitiesTable();
+
+ activities.addRemainderActivity("a1");
+ activities.addActivity(Duration.ofMinutes(7), "a2");
+
+ final Builder activity1 = ActivitiesTableExpectedRow.defaultRow()
+ .withDuration(Duration.ofMinutes(3))
+ .withRemainder(true)
+ .withComment("a1");
+ final Builder activity2 = ActivitiesTableExpectedRow.defaultRow()
+ .withDuration(Duration.ofMinutes(7))
+ .withRemainder(false)
+ .withComment("a2");
+
+ activities.table().assertContent(activity1.build(), activity2.build());
+ }
+
+ @Test
+ void remainderDurationUpdatedAtMinuteTick()
+ {
+ time().tickSeparateMinutes(11);
+
+ final ActivitiesTable activities = app().activitiesTable();
+
+ activities.addRemainderActivity("a1");
+ activities.addActivity(Duration.ofMinutes(7), "a2");
+
+ final Builder activity1 = ActivitiesTableExpectedRow.defaultRow()
+ .withDuration(Duration.ofMinutes(3))
+ .withRemainder(true)
+ .withComment("a1");
+ final Builder activity2 = ActivitiesTableExpectedRow.defaultRow()
+ .withDuration(Duration.ofMinutes(7))
+ .withRemainder(false)
+ .withComment("a2");
+
+ activities.table().assertContent(activity1.build(), activity2.build());
+
+ time().tickMinute();
+
+ activity1.withDuration(Duration.ofMinutes(4));
+ activities.table().assertContent(activity1.build(), activity2.build());
+ }
+
+ @Test
+ void toggleRemainderDurationUpdatedAtMinuteTick()
+ {
+ time().tickSeparateMinutes(11);
+
+ final ActivitiesTable activities = app().activitiesTable();
+
+ activities.addRemainderActivity("a1");
+ activities.addActivity(Duration.ofMinutes(7), "a2");
+
+ final Builder activity1 = ActivitiesTableExpectedRow.defaultRow()
+ .withDuration(Duration.ofMinutes(3))
+ .withRemainder(true)
+ .withComment("a1");
+ final Builder activity2 = ActivitiesTableExpectedRow.defaultRow()
+ .withDuration(Duration.ofMinutes(7))
+ .withRemainder(false)
+ .withComment("a2");
+
+ activities.table().assertContent(activity1.build(), activity2.build());
+
+ activities.toggleRemainder(1);
+
+ time().tickMinute();
+
+ activity2.withRemainder(true).withDuration(Duration.ofMinutes(8));
+ activity1.withRemainder(false);
+ activities.table().assertContent(activity1.build(), activity2.build());
}
@Test
@@ -70,7 +235,7 @@ void addActivitySelectRemainder()
{
addActivity();
- final JavaFxTable activitiesTable = lookupActivitiesTable();
+ final JavaFxTable activitiesTable = app().activitiesTable().table();
robot.clickOn(activitiesTable.getTableCell(0, "remainder"));
activitiesTable.assertRowContent(0, ActivitiesTableExpectedRow.defaultRow().withRemainder(true).build());
}
@@ -80,7 +245,7 @@ void addActivitySelectProject()
{
addActivity();
- final JavaFxTable activitiesTable = lookupActivitiesTable();
+ final JavaFxTable activitiesTable = app().activitiesTable().table();
final Node projectCell = activitiesTable.getTableCell(0, "project");
robot.doubleClickOn(projectCell).clickOn(projectCell).type(KeyCode.ENTER);
@@ -91,13 +256,15 @@ void addActivitySelectProject()
void addActivityForOtherDay()
{
final int rowTomorrow = time().getCurrentDayRowIndex() + 1;
+ final JavaFxTable table = app().activitiesTable().table();
+
app().genericDayTable().clickRow(rowTomorrow);
- app().activitiesTable().assertRowCount(0);
+ table.assertRowCount(0);
addActivity();
- app().activitiesTable().assertRowCount(1);
+ table.assertRowCount(1);
}
@Test
@@ -106,26 +273,43 @@ void activitiesTableUpdatedWhenSwitchingDays()
final int row = time().getCurrentDayRowIndex();
app().genericDayTable().clickRow(row + 1);
- app().activitiesTable().assertRowCount(0);
+ final JavaFxTable table = app().activitiesTable().table();
+ table.assertRowCount(0);
addActivity();
- app().activitiesTable().assertRowCount(1);
+ table.assertRowCount(1);
app().genericDayTable().clickRow(row);
- app().activitiesTable().assertRowCount(0);
+ table.assertRowCount(0);
+ }
+
+ @Test
+ void activitiesTableUpdatedWhenDayChanges()
+ {
+ time().tickMinute();
+
+ final ActivitiesTable activities = app().activitiesTable();
+ final JavaFxTable table = activities.table();
+ table.assertRowCount(0);
+
+ activities.addActivity();
+ table.assertRowCount(1);
+
+ time().tickDay();
+ table.assertRowCount(0);
}
private void addActivity()
{
time().tickMinute();
- final JavaFxTable activitiesTable = lookupActivitiesTable();
+ final ActivitiesTable activities = app().activitiesTable();
- clickAddActivityButton();
+ activities.addActivity();
final Builder expectedRowContent = ActivitiesTableExpectedRow.defaultRow();
- assertAll(() -> activitiesTable.assertRowCount(1),
- () -> activitiesTable.assertRowContent(0, expectedRowContent.build()));
+ assertAll(() -> activities.table().assertRowCount(1),
+ () -> activities.table().assertRowContent(0, expectedRowContent.build()));
}
private void selectCurrentDay()
@@ -136,22 +320,6 @@ private void selectCurrentDay()
robot.clickOn(dayTable.getTableRow(dayRowIndex));
}
- private void clickAddActivityButton()
- {
- final Button addActivityButton = getAddActivityButton();
- robot.clickOn(addActivityButton);
- }
-
- private Button getAddActivityButton()
- {
- return robot.lookup("#add-activity-button").queryButton();
- }
-
- private JavaFxTable lookupActivitiesTable()
- {
- return app().activitiesTable();
- }
-
@Override
@Start
void start(Stage stage)
diff --git a/jfxui/src/uiTest/java/org/itsallcode/whiterabbit/jfxui/TableCellEditTest.java b/jfxui/src/uiTest/java/org/itsallcode/whiterabbit/jfxui/TableCellEditTest.java
index fc635692..68cc1307 100644
--- a/jfxui/src/uiTest/java/org/itsallcode/whiterabbit/jfxui/TableCellEditTest.java
+++ b/jfxui/src/uiTest/java/org/itsallcode/whiterabbit/jfxui/TableCellEditTest.java
@@ -14,6 +14,7 @@
import org.itsallcode.whiterabbit.jfxui.testutil.DayTableExpectedRow;
import org.itsallcode.whiterabbit.jfxui.testutil.DayTableExpectedRow.Builder;
import org.itsallcode.whiterabbit.jfxui.testutil.TestUtil;
+import org.itsallcode.whiterabbit.jfxui.testutil.model.ActivitiesTable;
import org.itsallcode.whiterabbit.jfxui.testutil.model.JavaFxTable;
import org.itsallcode.whiterabbit.logic.model.json.DayType;
import org.junit.jupiter.api.Test;
@@ -112,14 +113,50 @@ void editingNotAbortedWhenMinuteChanges()
assertThat(commentCell.getText()).isEqualTo("new");
}
+ @Test
+ void editingActivitiesTableNotAbortedWhenMinuteChanges()
+ {
+ time().tickMinute();
+
+ final ActivitiesTable activities = app().activitiesTable();
+ activities.addActivity();
+
+ activities.table().clickRow(0);
+
+ TableCell, ?> commentCell = activities.getCommentCell(0);
+
+ robot.doubleClickOn(commentCell).write("tst").type(KeyCode.ENTER);
+
+ assertThat(commentCell.isEditing()).as("cell is editing").isFalse();
+
+ commentCell = activities.getCommentCell(0);
+ robot.doubleClickOn(commentCell).write("new");
+
+ assertThat(commentCell.isEditing()).as("cell is editing").isTrue();
+
+ time().tickMinute();
+
+ TestUtil.sleepShort();
+
+ assertThat(commentCell.isEditing()).as("cell is editing after minute tick").isTrue();
+ robot.type(KeyCode.ENTER);
+
+ assertThat(commentCell.getText()).isEqualTo("new");
+ }
+
private void assertCommentCellPersistedAfterCommitAction(Runnable commitAction)
{
+ time().tickMinute();
final LocalDate today = time().getCurrentDate();
final LocalTime now = time().getCurrentTimeMinutes();
final int rowIndex = time().getCurrentDayRowIndex();
final String comment = "tst";
- final Builder expectedCellValues = DayTableExpectedRow.defaultValues(today, DayType.WORK);
+ final Builder expectedCellValues = DayTableExpectedRow.defaultValues(today, DayType.WORK)
+ .withBegin(now)
+ .withEnd(now)
+ .withOvertimeToday(Duration.ofHours(-8))
+ .withTotalOvertime(Duration.ofHours(-8));
final JavaFxTable dayTable = app().genericDayTable();
@@ -136,10 +173,7 @@ private void assertCommentCellPersistedAfterCommitAction(Runnable commitAction)
assertAll(
() -> assertThat(commentCell.isEditing()).as("cell is editing").isFalse(),
- () -> dayTable.assertRowContent(rowIndex, expectedCellValues.withBegin(now).withEnd(now)
- .withOvertimeToday(Duration.ofHours(-8))
- .withTotalOvertime(Duration.ofHours(-8))
- .withComment(comment).build()));
+ () -> dayTable.assertRowContent(rowIndex, expectedCellValues.withComment(comment).build()));
}
private void assertCommentCellNotPersistedAfterFocusLostAction(Runnable focusLossAction)
diff --git a/jfxui/src/uiTest/java/org/itsallcode/whiterabbit/jfxui/testutil/TimeUtil.java b/jfxui/src/uiTest/java/org/itsallcode/whiterabbit/jfxui/testutil/TimeUtil.java
index 7ae7cc61..9564525d 100644
--- a/jfxui/src/uiTest/java/org/itsallcode/whiterabbit/jfxui/testutil/TimeUtil.java
+++ b/jfxui/src/uiTest/java/org/itsallcode/whiterabbit/jfxui/testutil/TimeUtil.java
@@ -21,6 +21,7 @@
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
+import java.util.stream.IntStream;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
@@ -89,11 +90,21 @@ public void tickSecond()
this.updateEverySecondRunnable.run();
}
+ public void tickSeparateMinutes(int minuteCount)
+ {
+ IntStream.range(0, minuteCount).forEach(i -> tickMinute());
+ }
+
public void tickMinute()
{
tickMinute(Duration.ofMinutes(1));
}
+ public void tickDay()
+ {
+ tickDay(LocalTime.of(8, 0));
+ }
+
public void tickDay(LocalTime time)
{
final LocalDateTime now = LocalDateTime.now(clockMock);
diff --git a/jfxui/src/uiTest/java/org/itsallcode/whiterabbit/jfxui/testutil/model/ActivitiesTable.java b/jfxui/src/uiTest/java/org/itsallcode/whiterabbit/jfxui/testutil/model/ActivitiesTable.java
new file mode 100644
index 00000000..486513ec
--- /dev/null
+++ b/jfxui/src/uiTest/java/org/itsallcode/whiterabbit/jfxui/testutil/model/ActivitiesTable.java
@@ -0,0 +1,102 @@
+package org.itsallcode.whiterabbit.jfxui.testutil.model;
+
+import java.time.Duration;
+
+import org.itsallcode.whiterabbit.jfxui.table.activities.ActivityPropertyAdapter;
+import org.itsallcode.whiterabbit.logic.service.project.Project;
+import org.testfx.api.FxRobot;
+
+import javafx.scene.Node;
+import javafx.scene.control.Button;
+import javafx.scene.control.TableCell;
+import javafx.scene.input.KeyCode;
+
+public class ActivitiesTable
+{
+ private final JavaFxTable table;
+ private final FxRobot robot;
+
+ ActivitiesTable(JavaFxTable table, FxRobot robot)
+ {
+ this.table = table;
+ this.robot = robot;
+ }
+
+ public Button getAddActivityButton()
+ {
+ return robot.lookup("#add-activity-button").queryButton();
+ }
+
+ public void addRemainderActivity(String comment)
+ {
+ addRemainderActivity(null, comment);
+ }
+
+ public void addRemainderActivity(Project project, String comment)
+ {
+ final int rowIndex = addActivity();
+
+ if (project != null)
+ {
+ final Node projectCell = table.getTableCell(rowIndex, "project");
+ robot.doubleClickOn(projectCell).clickOn(projectCell).clickOn(project.getLabel());
+ }
+ robot.clickOn(table.getTableCell(rowIndex, "remainder"));
+ robot.doubleClickOn(table.getTableCell(rowIndex, "comment")).write(comment).type(KeyCode.ENTER);
+ }
+
+ public void addActivity(Duration duration, String comment)
+ {
+ addActivity(null, duration, comment);
+ }
+
+ public void addActivity(Project project, Duration duration, String comment)
+ {
+ final int rowIndex = addActivity();
+
+ if (project != null)
+ {
+ final Node projectCell = table.getTableCell(rowIndex, "project");
+ robot.doubleClickOn(projectCell).clickOn(projectCell).clickOn(project.getLabel());
+ robot.clickOn(table.getTableCell(rowIndex, "duration"));
+ }
+
+ robot.doubleClickOn(table.getTableCell(rowIndex, "duration"))
+ .write("0:" + duration.toMinutes())
+ .type(KeyCode.ENTER);
+ robot.doubleClickOn(table.getTableCell(rowIndex, "comment")).write(comment).type(KeyCode.ENTER);
+ }
+
+ public void toggleRemainder(int rowIndex)
+ {
+ robot.clickOn(table.getTableCell(rowIndex, "remainder"));
+ }
+
+ public int addActivity()
+ {
+ final Button addActivityButton = getAddActivityButton();
+ robot.clickOn(addActivityButton);
+ return table.getRowCount() - 1;
+ }
+
+ public void removeActivity()
+ {
+ final Button removeActivityButton = getRemoveActivityButton();
+ robot.clickOn(removeActivityButton);
+ }
+
+ public Button getRemoveActivityButton()
+ {
+ return robot.lookup("#remove-activity-button").queryButton();
+ }
+
+ public TableCell, ?> getCommentCell(int rowIndex)
+ {
+ return table.getTableCell(rowIndex, "comment");
+ }
+
+ public JavaFxTable table()
+ {
+ return table;
+ }
+}
diff --git a/jfxui/src/uiTest/java/org/itsallcode/whiterabbit/jfxui/testutil/model/ApplicationHelper.java b/jfxui/src/uiTest/java/org/itsallcode/whiterabbit/jfxui/testutil/model/ApplicationHelper.java
index e4193543..fbf87592 100644
--- a/jfxui/src/uiTest/java/org/itsallcode/whiterabbit/jfxui/testutil/model/ApplicationHelper.java
+++ b/jfxui/src/uiTest/java/org/itsallcode/whiterabbit/jfxui/testutil/model/ApplicationHelper.java
@@ -29,11 +29,6 @@ public InterruptionDialog startInterruption()
return new InterruptionDialog(robot, window);
}
- public JavaFxTable activitiesTable()
- {
- return JavaFxTable.find(robot, "#activities-table", ActivityPropertyAdapter.class);
- }
-
public JavaFxTable genericDayTable()
{
return JavaFxTable.find(robot, "#day-table", DayRecordPropertyAdapter.class);
@@ -44,6 +39,16 @@ public DayTable dayTable()
return new DayTable(genericDayTable(), robot);
}
+ public ActivitiesTable activitiesTable()
+ {
+ return new ActivitiesTable(genericActivitiesTable(), robot);
+ }
+
+ private JavaFxTable genericActivitiesTable()
+ {
+ return JavaFxTable.find(robot, "#activities-table", ActivityPropertyAdapter.class);
+ }
+
public AutomaticInterruptionDialog assertAutomaticInterruption()
{
final Window dialogWindow = robot.window("Interruption detected");
diff --git a/jfxui/src/uiTest/java/org/itsallcode/whiterabbit/jfxui/testutil/model/JavaFxTable.java b/jfxui/src/uiTest/java/org/itsallcode/whiterabbit/jfxui/testutil/model/JavaFxTable.java
index 09a44d9c..d272055b 100644
--- a/jfxui/src/uiTest/java/org/itsallcode/whiterabbit/jfxui/testutil/model/JavaFxTable.java
+++ b/jfxui/src/uiTest/java/org/itsallcode/whiterabbit/jfxui/testutil/model/JavaFxTable.java
@@ -1,8 +1,13 @@
package org.itsallcode.whiterabbit.jfxui.testutil.model;
import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.jupiter.api.Assertions.assertAll;
+
+import java.util.ArrayList;
+import java.util.List;
import org.itsallcode.whiterabbit.jfxui.testutil.TableRowExpectedContent;
+import org.junit.jupiter.api.function.Executable;
import org.testfx.api.FxRobot;
import org.testfx.assertions.api.Assertions;
@@ -27,6 +32,18 @@ static JavaFxTable find(FxRobot robot, String query, Class rowType)
return new JavaFxTable<>(robot, robot.lookup(query).queryTableView());
}
+ public void assertContent(TableRowExpectedContent... expectedRows)
+ {
+ final List expectations = new ArrayList<>();
+ expectations.add(() -> assertRowCount(expectedRows.length));
+ for (int i = 0; i < expectedRows.length; i++)
+ {
+ final int row = i;
+ expectations.add(() -> assertRowContent(row, expectedRows[row]));
+ }
+ assertAll(expectations);
+ }
+
public void assertRowContent(final int rowIndex, final TableRowExpectedContent expectedRowContent)
{
Assertions.assertThat(table).containsRowAtIndex(rowIndex, expectedRowContent.expectedCellContent());
@@ -63,6 +80,11 @@ public void assertRowCount(int expectedRowCount)
Assertions.assertThat(table).hasExactlyNumRows(expectedRowCount);
}
+ public int getRowCount()
+ {
+ return table.getItems().size();
+ }
+
public int getSelectedRowIndex()
{
return table.getSelectionModel().selectedIndexProperty().get();
diff --git a/logic/src/main/java/org/itsallcode/whiterabbit/logic/model/DayActivities.java b/logic/src/main/java/org/itsallcode/whiterabbit/logic/model/DayActivities.java
index ea2c278c..fc3107ab 100644
--- a/logic/src/main/java/org/itsallcode/whiterabbit/logic/model/DayActivities.java
+++ b/logic/src/main/java/org/itsallcode/whiterabbit/logic/model/DayActivities.java
@@ -55,6 +55,12 @@ public List getAll()
.collect(toList());
}
+ public boolean isEmpty()
+ {
+ return getActivities().map(a -> false)
+ .findFirst().orElse(true);
+ }
+
private Stream getActivities()
{
return Optional.ofNullable(day.getActivities())
diff --git a/logic/src/main/java/org/itsallcode/whiterabbit/logic/service/project/Project.java b/logic/src/main/java/org/itsallcode/whiterabbit/logic/service/project/Project.java
index 95960c2c..301d6e0a 100644
--- a/logic/src/main/java/org/itsallcode/whiterabbit/logic/service/project/Project.java
+++ b/logic/src/main/java/org/itsallcode/whiterabbit/logic/service/project/Project.java
@@ -11,6 +11,18 @@ public class Project
@JsonbProperty("costCarrier")
private String costCarrier;
+ public Project(String projectId, String label, String costCarrier)
+ {
+ this.projectId = projectId;
+ this.label = label;
+ this.costCarrier = costCarrier;
+ }
+
+ public Project()
+ {
+ this(null, null, null);
+ }
+
public String getProjectId()
{
return projectId;
diff --git a/logic/src/test/resources/log4j2-test.xml b/logic/src/test/resources/log4j2-test.xml
index 4220c8e6..f5f8d44e 100644
--- a/logic/src/test/resources/log4j2-test.xml
+++ b/logic/src/test/resources/log4j2-test.xml
@@ -10,5 +10,8 @@
+
+
+
diff --git a/textui/src/main/resources/log4j2.xml b/textui/src/main/resources/log4j2.xml
index 4e41432f..1032b2f3 100644
--- a/textui/src/main/resources/log4j2.xml
+++ b/textui/src/main/resources/log4j2.xml
@@ -19,8 +19,8 @@
size="10 MB" />
-
+
diff --git a/webstart/content/whiterabbit.jnlp b/webstart/content/whiterabbit.jnlp
index 1fdb7791..5c7a1d27 100644
--- a/webstart/content/whiterabbit.jnlp
+++ b/webstart/content/whiterabbit.jnlp
@@ -1,14 +1,16 @@
-
+
WhiteRabbit Time Recording
- chπ
+ It's all code
+
+ A time recording tool
-
-
+
+