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 - - + +