diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/gameui/gameuicomponents/TaskbarController.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/gameui/gameuicomponents/TaskbarController.java index f393cb1..ded5048 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/gameui/gameuicomponents/TaskbarController.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/gameui/gameuicomponents/TaskbarController.java @@ -412,7 +412,7 @@ public class TaskbarController { boolean phaseChanged) { // boolean firstPlayerNewRound = - // phaseChanged && isFirstPreflopPlayerInputOnly(state, me); + // phaseChanged && isFirstPreflopPlayer(state, me); inputActionAllowed = false; if (!isMyTurn || isOutOrFinished || state == null || me == null) { setBetButtonVisible(false); @@ -433,20 +433,20 @@ public class TaskbarController { return; } - // if (isFirstPreflopPlayerInputOnly(state, me)) { - // setActionEnabled(betButton, true); - // setActionEnabled(callButton, false); - // setActionEnabled(foldButton, false); - // setActionEnabled(raiseButton, false); - // activateInputField(taskbarInput); - // inputActionAllowed = true; - // return; - // } + if (isFirstPreflopPlayer(state, me)) { + setActionEnabled(betButton, true); + setActionEnabled(callButton, false); + setActionEnabled(foldButton, false); + setActionEnabled(raiseButton, false); + activateInputField(taskbarInput); + inputActionAllowed = true; + return; + } if (isFirstPlayerOfPhase(state, me)) { setActionEnabled(betButton, true); setActionEnabled(callButton, false); - setActionEnabled(foldButton, false); + // setActionEnabled(foldButton, false); setActionEnabled(raiseButton, false); activateInputField(taskbarInput); inputActionAllowed = true; @@ -605,7 +605,7 @@ public class TaskbarController { * @return A boolean value indicating whether the current player is the first to act in the * pre-flop phase with only the initial blind layout in place. */ - private boolean isFirstPreflopPlayerInputOnly(GameState state, Player me) { + private boolean isFirstPreflopPlayer(GameState state, Player me) { if (state == null || me == null || !isPreflop(state.phase) || state.players == null) { return false; } @@ -1078,7 +1078,7 @@ public class TaskbarController { return ValidationResult.blocked("Bet must match at least the call amount"); } - if (isFirstPlayerOfPhase(state, me)) { + if (isFirstPreflopPlayer(state, me)) { if (required > FIRST_PLAYER_MAX_BET) { return ValidationResult.blocked( "First player cannot bet more than " + FIRST_PLAYER_MAX_BET); diff --git a/src/test/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/gameui/CasinoGameControllerUiTest.java b/src/test/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/gameui/CasinoGameControllerUiTest.java new file mode 100644 index 0000000..5fefeeb --- /dev/null +++ b/src/test/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/gameui/CasinoGameControllerUiTest.java @@ -0,0 +1,58 @@ +package ch.unibas.dmi.dbis.cs108.casono.client.ui.gameui; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import ch.unibas.dmi.dbis.cs108.casono.client.ui.gameui.gameuicomponents.TaskbarController; +import java.net.URL; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import javafx.application.Platform; +import javafx.fxml.FXMLLoader; +import javafx.scene.Parent; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +/** Smoke test for Taskbar UI. Ensures FXML loads and controller initializes without errors. */ +class TaskbarControllerSmokeTest { + + private static boolean fxStarted = false; + + /** Initializes JavaFX once before all tests. */ + @BeforeAll + static void startJavaFX() throws Exception { + if (!fxStarted) { + CountDownLatch latch = new CountDownLatch(1); + + try { + Platform.startup( + () -> { + Platform.setImplicitExit(false); + latch.countDown(); + }); + } catch (IllegalStateException ignored) { + latch.countDown(); + } + + assertTrue(latch.await(10, TimeUnit.SECONDS), "JavaFX did not start"); + fxStarted = true; + } + } + + /** + * Ensures FXML loads and controller initializes without errors. + */ + @Test + void taskbarLoadsWithoutErrors() throws Exception { + URL url = getClass().getResource("/ui-structure/gameuicomponents/Taskbar.fxml"); + assertNotNull(url); + + FXMLLoader loader = new FXMLLoader(url); + Parent root = loader.load(); + + TaskbarController controller = loader.getController(); + + assertNotNull(root); + assertNotNull(controller); + } +} diff --git a/src/test/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/gameui/gameuicomponents/TaskbarControllerInputValidationTest.java b/src/test/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/gameui/gameuicomponents/TaskbarControllerInputValidationTest.java new file mode 100644 index 0000000..0c44dc7 --- /dev/null +++ b/src/test/java/ch/unibas/dmi/dbis/cs108/casono/client/ui/gameui/gameuicomponents/TaskbarControllerInputValidationTest.java @@ -0,0 +1,175 @@ +package ch.unibas.dmi.dbis.cs108.casono.client.ui.gameui.gameuicomponents; + +import static org.junit.jupiter.api.Assertions.*; + +import ch.unibas.dmi.dbis.cs108.casono.client.game.*; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.net.URL; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import javafx.application.Platform; +import javafx.fxml.FXMLLoader; +import javafx.scene.Parent; +import javafx.scene.control.Button; +import javafx.scene.control.TextField; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +/** + * TODO: Refactor UI tests to avoid reflection and test behavior via public APIs or real UI actions + * (preferably using TestFX) for better maintainability and robustness. + * + *
These tests verify: - input validation behavior - UI state updates based on game state -
+ * correct enabling/disabling of action buttons
+ */
+class TaskbarControllerInputValidationTest {
+
+ private static boolean fxStarted = false;
+
+ /** Initializes JavaFX once before all tests. */
+ @BeforeAll
+ static void initJavaFX() throws Exception {
+ if (fxStarted) {
+ return;
+ }
+
+ CountDownLatch latch = new CountDownLatch(1);
+
+ try {
+ Platform.startup(
+ () -> {
+ Platform.setImplicitExit(false);
+ latch.countDown();
+ });
+ } catch (IllegalStateException ignored) {
+ latch.countDown();
+ }
+
+ latch.await(10, TimeUnit.SECONDS);
+ fxStarted = true;
+ }
+
+ /** Ensures invalid input values do not enable the BET button. */
+ @Test
+ void invalidInputShouldBeRejected() throws Exception {
+ Fixture f = load();
+
+ String[] invalidInputs = {"", "abc", "-1", "999999999999"};
+
+ for (String input : invalidInputs) {
+ setText(f.input, input);
+
+ invokePrivate(f.controller, "refreshBetInputUi");
+
+ Button bet = get(f.controller, "betButton", Button.class);
+
+ assertFalse(bet.isVisible(), "Invalid input must not enable BET button: " + input);
+ }
+ }
+
+ /** Ensures valid input enables the BET button. */
+ @Test
+ void validInputShouldEnableBet() throws Exception {
+ Fixture f = load();
+
+ GameState state = createGameState();
+
+ setField(f.controller, "myPlayerId", PlayerId.of("me"));
+ setField(f.controller, "lastState", state);
+ setField(f.controller, "inputActionAllowed", true);
+
+ f.controller.update(state, PlayerId.of("me"));
+
+ setText(f.input, "200");
+
+ invokePrivate(f.controller, "refreshBetInputUi");
+
+ Button bet = get(f.controller, "betButton", Button.class);
+
+ assertTrue(bet.isVisible(), "BET button should be enabled for valid input");
+ }
+
+ /** Ensures all UI actions are disabled when the game is finished. */
+ @Test
+ void finishedStateShouldDisableUi() throws Exception {
+ Fixture f = load();
+
+ GameState state = createGameState();
+ state.phase = "FINISHED";
+
+ f.controller.update(state, PlayerId.of("me"));
+
+ assertTrue(f.input.isDisabled());
+ assertTrue(get(f.controller, "callButton", Button.class).isDisabled());
+ assertTrue(get(f.controller, "foldButton", Button.class).isDisabled());
+ assertTrue(get(f.controller, "raiseButton", Button.class).isDisabled());
+ }
+
+ /** Creates a minimal game state for testing. */
+ private GameState createGameState() {
+ GameState state = new GameState();
+
+ Player me = new Player(PlayerId.of("me"), 20000);
+ Player other = new Player(PlayerId.of("other"), 20000);
+
+ state.players = List.of(me, other);
+ state.phase = "PREFLOP";
+ state.currentBet = 200;
+ state.activePlayer = 0;
+
+ return state;
+ }
+
+ /** Loads the JavaFX controller and its UI components. */
+ private Fixture load() throws Exception {
+ URL url = getClass().getResource("/ui-structure/gameuicomponents/Taskbar.fxml");
+ assertNotNull(url);
+
+ FXMLLoader loader = new FXMLLoader(url);
+ Parent root = loader.load();
+
+ root.applyCss();
+ root.layout();
+
+ TaskbarController controller = loader.getController();
+
+ TextField input = get(controller, "taskbarInput", TextField.class);
+ Button bet = get(controller, "betButton", Button.class);
+
+ return new Fixture(controller, input, bet);
+ }
+
+ private record Fixture(TaskbarController controller, TextField input, Button betButton) {}
+
+ private static void setText(TextField field, String value) {
+ field.setText(value);
+ }
+
+ private static void invokePrivate(Object obj, String method) throws Exception {
+ Method m = obj.getClass().getDeclaredMethod(method);
+ m.setAccessible(true);
+ m.invoke(obj);
+ }
+
+ private static