From 69c189bb44c356fa65416ef4058fb0214d9200c2 Mon Sep 17 00:00:00 2001 From: mp1282 Date: Mon, 23 Mar 2026 14:22:30 +0000 Subject: [PATCH 1/2] feat: TimerActionComponent to schedule actions. With TimerActionComponentTest for tests and sample application (PongVsComputerApp) to see usage. --- .../component/TimerActionComponent.java | 86 +++++++ .../component/TimerActionComponentTest.java | 240 ++++++++++++++++++ .../components/PongVsComputerApp.java | 150 +++++++++++ 3 files changed, 476 insertions(+) create mode 100644 fxgl-entity/src/main/java/com/almasb/fxgl/entity/component/TimerActionComponent.java create mode 100644 fxgl-entity/src/test/java/com/almasb/fxgl/entity/component/TimerActionComponentTest.java create mode 100644 fxgl-samples/src/main/java/intermediate/components/PongVsComputerApp.java diff --git a/fxgl-entity/src/main/java/com/almasb/fxgl/entity/component/TimerActionComponent.java b/fxgl-entity/src/main/java/com/almasb/fxgl/entity/component/TimerActionComponent.java new file mode 100644 index 0000000000..73596db5fa --- /dev/null +++ b/fxgl-entity/src/main/java/com/almasb/fxgl/entity/component/TimerActionComponent.java @@ -0,0 +1,86 @@ +/* + * FXGL - JavaFX Game Library. The MIT License (MIT). + * Copyright (c) AlmasB (almaslvl@gmail.com). + * See LICENSE for details. + */ + +package com.almasb.fxgl.entity.component; + +import com.almasb.fxgl.time.Timer; +import com.almasb.fxgl.time.TimerAction; +import javafx.beans.property.ReadOnlyBooleanProperty; +import javafx.util.Duration; + +/** + * Component to schedule the execution of actions. + * + * @implNote - A wrapper around a Timer class, as a component. + * + * @author Michael Pearson (https://github.com/michqql/) + */ +public class TimerActionComponent extends Component { + + private final Timer timer = new Timer(); + + @Override + public void onUpdate(double tpf) { + super.onUpdate(tpf); + this.timer.update(tpf); + } + + /** + * The Runnable [action] will be scheduled to start at given [interval]. + * The action will start for the first time after given interval. + * The action will be scheduled unlimited number of times unless user cancels it + * via the returned action object. + * + * @return timer action + */ + public TimerAction runAtInterval(Runnable action, Duration interval) { + return timer.runAtInterval(action, interval); + } + + /** + * The Runnable [action] will be scheduled to start at given [interval]. + * The action will start for the first time after given interval. + * The action will be scheduled [limit] number of times unless user cancels it + * via the returned action object. + * + * @return timer action + */ + public TimerAction runAtInterval(Runnable action, Duration interval, int limit) { + return timer.runAtInterval(action, interval, limit); + } + + /** + * The Runnable [action] will be scheduled to start at given [interval]. + * The Runnable action will be scheduled IFF + * [whileCondition] is initially true. + * The action will start for the first time after given interval. + * The action will be removed from schedule when [whileCondition] becomes "false". + * Note: you must retain the reference to the [whileCondition] property to avoid it being + * garbage collected, otherwise the [action] may never stop. + * + * @return timer action + */ + public TimerAction runAtIntervalWhile(Runnable action, Duration interval, ReadOnlyBooleanProperty whileCondition) { + return timer.runAtIntervalWhile(action, interval, whileCondition); + } + + /** + * The Runnable [action] will be scheduled to run once after given [delay]. + * The action can be cancelled before it starts via the returned action object. + * + * @return timer action + */ + public TimerAction runOnceAfter(Runnable action, Duration delay) { + return timer.runOnceAfter(action, delay); + } + + /** + * Remove all scheduled actions. + */ + public void clear() { + timer.clear(); + } +} diff --git a/fxgl-entity/src/test/java/com/almasb/fxgl/entity/component/TimerActionComponentTest.java b/fxgl-entity/src/test/java/com/almasb/fxgl/entity/component/TimerActionComponentTest.java new file mode 100644 index 0000000000..36e08e34e3 --- /dev/null +++ b/fxgl-entity/src/test/java/com/almasb/fxgl/entity/component/TimerActionComponentTest.java @@ -0,0 +1,240 @@ +/* + * FXGL - JavaFX Game Library. The MIT License (MIT). + * Copyright (c) AlmasB (almaslvl@gmail.com). + * See LICENSE for details. + */ + +package com.almasb.fxgl.entity.component; + +import com.almasb.fxgl.time.TimerAction; +import javafx.beans.binding.BooleanBinding; +import javafx.beans.property.*; +import javafx.util.Duration; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Tests for the {@link TimerActionComponent} class. + * + * @author Michael Pearson (https://github.com/michqql/) + */ +public class TimerActionComponentTest { + + /* Counter class to allow for atomic operations from Runnable action */ + private static class Counter { + private int value; + + int get () { return value ; } + void reset () { value = 0; } + void increment() { value++ ; } + } + + private final Counter executionCounter = new Counter(); + private final Runnable action = executionCounter::increment; + + private TimerActionComponent timerActionComponent; + + @BeforeEach + public void setUp() { + timerActionComponent = new TimerActionComponent(); + executionCounter.reset(); + } + + /** + * Tests that the action is ran multiple times as the interval elapses. + * Tests the method {@link TimerActionComponent#runAtInterval(Runnable, Duration, int) runAtInterval}. + */ + @Test + public void testRunAtInterval() { + timerActionComponent.runAtInterval(action, Duration.seconds(0.5f)); + + assertEquals(0, executionCounter.get()); + timerActionComponent.onUpdate(0.5f); + assertEquals(1, executionCounter.get()); + timerActionComponent.onUpdate(0.4f); + assertEquals(1, executionCounter.get()); + timerActionComponent.onUpdate(0.2f); + assertEquals(2, executionCounter.get()); + } + + /** + * Tests that the repeating action will not be executed after being cancelled. + */ + @Test + public void testRunAtIntervalThenCancel() { + TimerAction timerAction = timerActionComponent.runAtInterval(action, Duration.seconds(0.5f)); + + assertEquals(0, executionCounter.get()); + timerActionComponent.onUpdate(0.5f); + assertEquals(1, executionCounter.get()); + + timerAction.expire(); + + timerActionComponent.onUpdate(0.5f); + assertEquals(1, executionCounter.get()); + } + + /** + * Tests that the action can be executed multiple times. + */ + @Test + public void testRunAtIntervalInLoop() { + timerActionComponent.runAtInterval(action, Duration.seconds(0.5f)); + + for(int i = 0; i < 10; ++i) { + timerActionComponent.onUpdate(0.5f); + assertEquals(i + 1, executionCounter.get()); + } + } + + /** + * Tests that the action expires with the limit and will not execute again. + */ + @Test + public void testRunAtIntervalWithLimit() { + timerActionComponent.runAtInterval(action, Duration.seconds(0.5f), 4); + + for(int i = 0; i < 10; ++i) { + timerActionComponent.onUpdate(0.5f); + } + + assertEquals(4, executionCounter.get()); + } + + /** + * Tests that the action expires when the condition becomes false. + */ + @Test + public void testRunAtIntervalConditional() { + IntegerProperty iterationCount = new SimpleIntegerProperty(); + BooleanBinding condition = iterationCount.lessThan(5); + + BooleanProperty conditionProperty = new SimpleBooleanProperty(); + conditionProperty.bind(condition); + + timerActionComponent.runAtIntervalWhile(action, Duration.seconds(0.25f), conditionProperty); + + for(int i = 0; i < 10; ++i) { + timerActionComponent.onUpdate(0.5f); + iterationCount.set(iterationCount.get() + 1); + } + + assertEquals(5, executionCounter.get()); + } + + /** + * Tests that the conditional action can be cancelled by the user. + */ + @Test + public void testRunAtIntervalConditionalCancelledEarly() { + IntegerProperty iterationCount = new SimpleIntegerProperty(); + BooleanBinding condition = iterationCount.lessThan(5); + + BooleanProperty conditionProperty = new SimpleBooleanProperty(); + conditionProperty.bind(condition); + + TimerAction timerAction = timerActionComponent.runAtIntervalWhile(action, Duration.seconds(0.25f), conditionProperty); + + for(int i = 0; i < 10; ++i) { + timerActionComponent.onUpdate(0.5f); + iterationCount.set(iterationCount.get() + 1); + + /* Cancel on the 3rd iteration */ + if(i == 2) + timerAction.expire(); + } + + assertEquals(3, executionCounter.get()); + } + + /** + * Tests that the action is ran exactly once after the delay has elapsed + * when using {@link TimerActionComponent#runOnceAfter(Runnable, Duration) runOnceAfter}. + */ + @Test + public void testRunOnceAfter() { + timerActionComponent.runOnceAfter(action, Duration.seconds(1)); + + assertEquals(0, executionCounter.get()); + timerActionComponent.onUpdate(1.0f); + assertEquals(1, executionCounter.get()); + timerActionComponent.onUpdate(1.0f); + assertEquals(1, executionCounter.get()); + } + + /** + * Tests that the action is only ran after the delay has elapsed, + * when the component has already seen time elapse. + * Tests the method {@link TimerActionComponent#runOnceAfter(Runnable, Duration) runOnceAfter}. + */ + @Test + public void testRunOnceAfterWithStartingTime() { + timerActionComponent.onUpdate(2.0f); + timerActionComponent.runOnceAfter(action, Duration.seconds(1)); + + assertEquals(0, executionCounter.get()); + timerActionComponent.onUpdate(1.0f); + assertEquals(1, executionCounter.get()); + } + + /** + * Tests that the action is not ran if not enough time elapses. + * Tests the method {@link TimerActionComponent#runOnceAfter(Runnable, Duration) runOnceAfter}. + */ + @Test + public void testRunOnceNotElapsed() { + timerActionComponent.runOnceAfter(action, Duration.seconds(1)); + + assertEquals(0, executionCounter.get()); + timerActionComponent.onUpdate(0.999f); + assertEquals(0, executionCounter.get()); + } + + /** + * Tests that the action is not executed if cancelled before the delay elapses. + * Tests the method {@link TimerActionComponent#runOnceAfter(Runnable, Duration) runOnceAfter}. + */ + @Test + public void testRunOnceCancelled() { + TimerAction timerAction = timerActionComponent.runOnceAfter(action, Duration.seconds(1)); + timerAction.expire(); + + assertEquals(0, executionCounter.get()); + timerActionComponent.onUpdate(1.0f); + assertEquals(0, executionCounter.get()); + } + + /** + * Tests that multiple actions can be scheduled. + */ + @Test + public void testRunOnceWithMultiple() { + timerActionComponent.runOnceAfter(action, Duration.seconds(0.1f)); + timerActionComponent.runOnceAfter(action, Duration.seconds(0.2f)); + timerActionComponent.runOnceAfter(action, Duration.seconds(0.29f)); /* Floating point cannot represent 0.3 well */ + timerActionComponent.runOnceAfter(action, Duration.seconds(0.4f)); + timerActionComponent.runOnceAfter(action, Duration.seconds(0.5f)); + + + for(int i = 0; i < 5; ++i) { + timerActionComponent.onUpdate(0.1f); + assertEquals(i + 1, executionCounter.get()); + } + } + + /** + * Tests that clearing the component will remove all actions. + */ + @Test + public void testClear() { + timerActionComponent.runAtInterval(action, Duration.seconds(1)); + timerActionComponent.runOnceAfter(action, Duration.seconds(0.1f)); + + timerActionComponent.clear(); + timerActionComponent.onUpdate(1.0f); + assertEquals(0, executionCounter.get()); + } + +} diff --git a/fxgl-samples/src/main/java/intermediate/components/PongVsComputerApp.java b/fxgl-samples/src/main/java/intermediate/components/PongVsComputerApp.java new file mode 100644 index 0000000000..84611ef7d3 --- /dev/null +++ b/fxgl-samples/src/main/java/intermediate/components/PongVsComputerApp.java @@ -0,0 +1,150 @@ +/* + * FXGL - JavaFX Game Library. The MIT License (MIT). + * Copyright (c) AlmasB (almaslvl@gmail.com). + * See LICENSE for details. + */ + +package intermediate.components; + +import com.almasb.fxgl.app.GameApplication; +import com.almasb.fxgl.app.GameSettings; +import com.almasb.fxgl.entity.Entity; +import com.almasb.fxgl.entity.component.TimerActionComponent; +import javafx.geometry.Point2D; +import javafx.scene.input.KeyCode; +import javafx.scene.paint.Color; +import javafx.scene.shape.Rectangle; +import javafx.scene.text.Text; +import javafx.util.Duration; + +import java.util.Map; + +import static com.almasb.fxgl.dsl.FXGL.*; + +public class PongVsComputerApp extends GameApplication { + + private static final int PADDLE_WIDTH = 30; + private static final int PADDLE_HEIGHT = 100; + private static final int BALL_SIZE = 20; + private static final int PADDLE_SPEED = 5; + private static final int BALL_SPEED = 5; + + private Entity ball; + private Entity playerPaddle; + private Entity computerPaddle; + + @Override + protected void initSettings(GameSettings settings) { + settings.setTitle("Impossible Pong"); + } + + @Override + protected void initInput() { + onKey(KeyCode.W, () -> playerPaddle.translateY(-PADDLE_SPEED)); + onKey(KeyCode.S, () -> playerPaddle.translateY(PADDLE_SPEED)); + } + + @Override + protected void initGameVars(Map vars) { + vars.put("score1", 0); + vars.put("score2", 0); + } + + @Override + protected void initGame() { + ball = spawnBall(getAppWidth() / 2 - BALL_SIZE / 2, getAppHeight() / 2 - BALL_SIZE / 2); + playerPaddle = spawnPlayerBat(0, getAppHeight() / 2 - PADDLE_HEIGHT / 2); + computerPaddle = spawnComputerBat(getAppWidth() - PADDLE_WIDTH, getAppHeight() / 2 - PADDLE_HEIGHT / 2); + } + + @Override + protected void initUI() { + Text textScore1 = getUIFactoryService().newText("", Color.BLACK, 22); + Text textScore2 = getUIFactoryService().newText("", Color.BLACK, 22); + + textScore1.textProperty().bind(getip("score1").asString()); + textScore2.textProperty().bind(getip("score2").asString()); + + addUINode(textScore1, 10, 50); + addUINode(textScore2, getAppWidth() - 30, 50); + } + + @Override + protected void onUpdate(double tpf) { + Point2D velocity = ball.getObject("velocity"); + ball.translate(velocity); + + if (ball.getX() == playerPaddle.getRightX() + && ball.getY() < playerPaddle.getBottomY() + && ball.getBottomY() > playerPaddle.getY()) { + ball.setProperty("velocity", new Point2D(-velocity.getX(), velocity.getY())); + } + + if (ball.getRightX() == computerPaddle.getX() + && ball.getY() < computerPaddle.getBottomY() + && ball.getBottomY() > computerPaddle.getY()) { + ball.setProperty("velocity", new Point2D(-velocity.getX(), velocity.getY())); + } + + if (ball.getX() <= 0) { + inc("score2", +1); + resetBall(); + } + + if (ball.getRightX() >= getAppWidth()) { + inc("score1", +1); + resetBall(); + } + + if (ball.getY() <= 0) { + ball.setY(0); + ball.setProperty("velocity", new Point2D(velocity.getX(), -velocity.getY())); + } + + if (ball.getBottomY() >= getAppHeight()) { + ball.setY(getAppHeight() - BALL_SIZE); + ball.setProperty("velocity", new Point2D(velocity.getX(), -velocity.getY())); + } + } + + private Entity spawnPlayerBat(double x, double y) { + return entityBuilder() + .at(x, y) + .viewWithBBox(new Rectangle(PADDLE_WIDTH, PADDLE_HEIGHT)) + .buildAndAttach(); + } + + private Entity spawnComputerBat(double x, double y) { + TimerActionComponent timerComponent = new TimerActionComponent(); + + /* Move the Y position of the computer bat to the Y position of the ball */ + timerComponent.runAtInterval(() -> { + Entity ball = timerComponent.getEntity().getObject("ballEntity"); + timerComponent.getEntity().setY(ball.getY() - timerComponent.getEntity().getHeight() / 2f); + }, Duration.seconds(1 / 60f)); + + return entityBuilder() + .at(x, y) + .viewWithBBox(new Rectangle(PADDLE_WIDTH, PADDLE_HEIGHT)) + .with(timerComponent) + .with("ballEntity", ball) + .buildAndAttach(); + } + + private Entity spawnBall(double x, double y) { + return entityBuilder() + .at(x, y) + .viewWithBBox(new Rectangle(BALL_SIZE, BALL_SIZE)) + .with("velocity", new Point2D(BALL_SPEED, BALL_SPEED)) + .buildAndAttach(); + } + + private void resetBall() { + ball.setPosition(getAppWidth() / 2 - BALL_SIZE / 2, getAppHeight() / 2 - BALL_SIZE / 2); + ball.setProperty("velocity", new Point2D(BALL_SPEED, BALL_SPEED)); + } + + public static void main(String[] args) { + launch(args); + } +} From 3cbd3381f8fdaa7173b5c9fe3d4fb9e64a834cc7 Mon Sep 17 00:00:00 2001 From: mp1282 Date: Tue, 14 Apr 2026 17:52:05 +0100 Subject: [PATCH 2/2] feat: TimerActionComponent to schedule actions. Amended comments in TimerActionComponent and changed the TimerActionComponentTest. --- .../component/TimerActionComponent.java | 5 +- .../component/TimerActionComponentTest.java | 69 +++++++++++++------ 2 files changed, 51 insertions(+), 23 deletions(-) diff --git a/fxgl-entity/src/main/java/com/almasb/fxgl/entity/component/TimerActionComponent.java b/fxgl-entity/src/main/java/com/almasb/fxgl/entity/component/TimerActionComponent.java index 73596db5fa..a3778dfaf0 100644 --- a/fxgl-entity/src/main/java/com/almasb/fxgl/entity/component/TimerActionComponent.java +++ b/fxgl-entity/src/main/java/com/almasb/fxgl/entity/component/TimerActionComponent.java @@ -13,18 +13,19 @@ /** * Component to schedule the execution of actions. + * The timer's time per frame (TPF) is tied to the entity's TPF. + * The entity's TPF can be modified by {@link com.almasb.fxgl.entity.components.TimeComponent}. * * @implNote - A wrapper around a Timer class, as a component. * * @author Michael Pearson (https://github.com/michqql/) */ -public class TimerActionComponent extends Component { +public final class TimerActionComponent extends Component { private final Timer timer = new Timer(); @Override public void onUpdate(double tpf) { - super.onUpdate(tpf); this.timer.update(tpf); } diff --git a/fxgl-entity/src/test/java/com/almasb/fxgl/entity/component/TimerActionComponentTest.java b/fxgl-entity/src/test/java/com/almasb/fxgl/entity/component/TimerActionComponentTest.java index 36e08e34e3..94a180de0d 100644 --- a/fxgl-entity/src/test/java/com/almasb/fxgl/entity/component/TimerActionComponentTest.java +++ b/fxgl-entity/src/test/java/com/almasb/fxgl/entity/component/TimerActionComponentTest.java @@ -10,7 +10,6 @@ import javafx.beans.binding.BooleanBinding; import javafx.beans.property.*; import javafx.util.Duration; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.*; @@ -22,32 +21,16 @@ */ public class TimerActionComponentTest { - /* Counter class to allow for atomic operations from Runnable action */ - private static class Counter { - private int value; - - int get () { return value ; } - void reset () { value = 0; } - void increment() { value++ ; } - } - - private final Counter executionCounter = new Counter(); - private final Runnable action = executionCounter::increment; - - private TimerActionComponent timerActionComponent; - - @BeforeEach - public void setUp() { - timerActionComponent = new TimerActionComponent(); - executionCounter.reset(); - } - /** * Tests that the action is ran multiple times as the interval elapses. * Tests the method {@link TimerActionComponent#runAtInterval(Runnable, Duration, int) runAtInterval}. */ @Test public void testRunAtInterval() { + final TimerActionComponent timerActionComponent = new TimerActionComponent(); + final IntegerProperty executionCounter = new SimpleIntegerProperty(); + final Runnable action = () -> executionCounter.set(executionCounter.get() + 1); + timerActionComponent.runAtInterval(action, Duration.seconds(0.5f)); assertEquals(0, executionCounter.get()); @@ -64,6 +47,10 @@ public void testRunAtInterval() { */ @Test public void testRunAtIntervalThenCancel() { + final TimerActionComponent timerActionComponent = new TimerActionComponent(); + final IntegerProperty executionCounter = new SimpleIntegerProperty(); + final Runnable action = () -> executionCounter.set(executionCounter.get() + 1); + TimerAction timerAction = timerActionComponent.runAtInterval(action, Duration.seconds(0.5f)); assertEquals(0, executionCounter.get()); @@ -81,6 +68,10 @@ public void testRunAtIntervalThenCancel() { */ @Test public void testRunAtIntervalInLoop() { + final TimerActionComponent timerActionComponent = new TimerActionComponent(); + final IntegerProperty executionCounter = new SimpleIntegerProperty(); + final Runnable action = () -> executionCounter.set(executionCounter.get() + 1); + timerActionComponent.runAtInterval(action, Duration.seconds(0.5f)); for(int i = 0; i < 10; ++i) { @@ -94,6 +85,10 @@ public void testRunAtIntervalInLoop() { */ @Test public void testRunAtIntervalWithLimit() { + final TimerActionComponent timerActionComponent = new TimerActionComponent(); + final IntegerProperty executionCounter = new SimpleIntegerProperty(); + final Runnable action = () -> executionCounter.set(executionCounter.get() + 1); + timerActionComponent.runAtInterval(action, Duration.seconds(0.5f), 4); for(int i = 0; i < 10; ++i) { @@ -108,6 +103,10 @@ public void testRunAtIntervalWithLimit() { */ @Test public void testRunAtIntervalConditional() { + final TimerActionComponent timerActionComponent = new TimerActionComponent(); + final IntegerProperty executionCounter = new SimpleIntegerProperty(); + final Runnable action = () -> executionCounter.set(executionCounter.get() + 1); + IntegerProperty iterationCount = new SimpleIntegerProperty(); BooleanBinding condition = iterationCount.lessThan(5); @@ -129,6 +128,10 @@ public void testRunAtIntervalConditional() { */ @Test public void testRunAtIntervalConditionalCancelledEarly() { + final TimerActionComponent timerActionComponent = new TimerActionComponent(); + final IntegerProperty executionCounter = new SimpleIntegerProperty(); + final Runnable action = () -> executionCounter.set(executionCounter.get() + 1); + IntegerProperty iterationCount = new SimpleIntegerProperty(); BooleanBinding condition = iterationCount.lessThan(5); @@ -155,6 +158,10 @@ public void testRunAtIntervalConditionalCancelledEarly() { */ @Test public void testRunOnceAfter() { + final TimerActionComponent timerActionComponent = new TimerActionComponent(); + final IntegerProperty executionCounter = new SimpleIntegerProperty(); + final Runnable action = () -> executionCounter.set(executionCounter.get() + 1); + timerActionComponent.runOnceAfter(action, Duration.seconds(1)); assertEquals(0, executionCounter.get()); @@ -171,6 +178,10 @@ public void testRunOnceAfter() { */ @Test public void testRunOnceAfterWithStartingTime() { + final TimerActionComponent timerActionComponent = new TimerActionComponent(); + final IntegerProperty executionCounter = new SimpleIntegerProperty(); + final Runnable action = () -> executionCounter.set(executionCounter.get() + 1); + timerActionComponent.onUpdate(2.0f); timerActionComponent.runOnceAfter(action, Duration.seconds(1)); @@ -185,6 +196,10 @@ public void testRunOnceAfterWithStartingTime() { */ @Test public void testRunOnceNotElapsed() { + final TimerActionComponent timerActionComponent = new TimerActionComponent(); + final IntegerProperty executionCounter = new SimpleIntegerProperty(); + final Runnable action = () -> executionCounter.set(executionCounter.get() + 1); + timerActionComponent.runOnceAfter(action, Duration.seconds(1)); assertEquals(0, executionCounter.get()); @@ -198,6 +213,10 @@ public void testRunOnceNotElapsed() { */ @Test public void testRunOnceCancelled() { + final TimerActionComponent timerActionComponent = new TimerActionComponent(); + final IntegerProperty executionCounter = new SimpleIntegerProperty(); + final Runnable action = () -> executionCounter.set(executionCounter.get() + 1); + TimerAction timerAction = timerActionComponent.runOnceAfter(action, Duration.seconds(1)); timerAction.expire(); @@ -211,6 +230,10 @@ public void testRunOnceCancelled() { */ @Test public void testRunOnceWithMultiple() { + final TimerActionComponent timerActionComponent = new TimerActionComponent(); + final IntegerProperty executionCounter = new SimpleIntegerProperty(); + final Runnable action = () -> executionCounter.set(executionCounter.get() + 1); + timerActionComponent.runOnceAfter(action, Duration.seconds(0.1f)); timerActionComponent.runOnceAfter(action, Duration.seconds(0.2f)); timerActionComponent.runOnceAfter(action, Duration.seconds(0.29f)); /* Floating point cannot represent 0.3 well */ @@ -229,6 +252,10 @@ public void testRunOnceWithMultiple() { */ @Test public void testClear() { + final TimerActionComponent timerActionComponent = new TimerActionComponent(); + final IntegerProperty executionCounter = new SimpleIntegerProperty(); + final Runnable action = () -> executionCounter.set(executionCounter.get() + 1); + timerActionComponent.runAtInterval(action, Duration.seconds(1)); timerActionComponent.runOnceAfter(action, Duration.seconds(0.1f));