From 94981d9cff17151873d87933717a2ce39d8ecb37 Mon Sep 17 00:00:00 2001 From: shahdlala66 Date: Mon, 30 Mar 2026 22:32:57 +0200 Subject: [PATCH] test: added UI model tests --- .../test/scala/de/nowchess/ui/MainTest.scala | 22 +++ .../nowchess/ui/terminal/TerminalUITest.scala | 145 ++++++++++++++++++ 2 files changed, 167 insertions(+) create mode 100644 modules/ui/src/test/scala/de/nowchess/ui/MainTest.scala create mode 100644 modules/ui/src/test/scala/de/nowchess/ui/terminal/TerminalUITest.scala diff --git a/modules/ui/src/test/scala/de/nowchess/ui/MainTest.scala b/modules/ui/src/test/scala/de/nowchess/ui/MainTest.scala new file mode 100644 index 0000000..dea2b2f --- /dev/null +++ b/modules/ui/src/test/scala/de/nowchess/ui/MainTest.scala @@ -0,0 +1,22 @@ +package de.nowchess.ui + +import org.scalatest.funsuite.AnyFunSuite +import org.scalatest.matchers.should.Matchers +import java.io.{ByteArrayInputStream, ByteArrayOutputStream} + +class MainTest extends AnyFunSuite with Matchers { + + test("main should execute and quit immediately when fed 'quit'") { + val in = new ByteArrayInputStream("quit\n".getBytes) + val out = new ByteArrayOutputStream() + + Console.withIn(in) { + Console.withOut(out) { + Main.main(Array.empty) + } + } + + val output = out.toString + output should include ("Game over. Goodbye!") + } +} diff --git a/modules/ui/src/test/scala/de/nowchess/ui/terminal/TerminalUITest.scala b/modules/ui/src/test/scala/de/nowchess/ui/terminal/TerminalUITest.scala new file mode 100644 index 0000000..897a729 --- /dev/null +++ b/modules/ui/src/test/scala/de/nowchess/ui/terminal/TerminalUITest.scala @@ -0,0 +1,145 @@ +package de.nowchess.ui.terminal + +import org.scalatest.funsuite.AnyFunSuite +import org.scalatest.matchers.should.Matchers +import java.io.{ByteArrayInputStream, ByteArrayOutputStream} +import de.nowchess.chess.engine.GameEngine +import de.nowchess.chess.observer.* +import de.nowchess.api.board.{Board, Color} +import de.nowchess.chess.logic.GameHistory + +class TerminalUITest extends AnyFunSuite with Matchers { + + test("TerminalUI should start, print initial state, and correctly respond to 'q'") { + val in = new ByteArrayInputStream("q\n".getBytes) + val out = new ByteArrayOutputStream() + + val engine = new GameEngine() + val ui = new TerminalUI(engine) + + Console.withIn(in) { + Console.withOut(out) { + ui.start() + } + } + + val output = out.toString + output should include("White's turn.") + output should include("Game over. Goodbye!") + } + + test("TerminalUI should ignore empty inputs and re-print prompt") { + val in = new ByteArrayInputStream("\nq\n".getBytes) + val out = new ByteArrayOutputStream() + + val engine = new GameEngine() + val ui = new TerminalUI(engine) + + Console.withIn(in) { + Console.withOut(out) { + ui.start() + } + } + + val output = out.toString + // Prompt appears three times: Initial, after empty, on exit. + output.split("White's turn.").length should be > 2 + } + + test("TerminalUI printPrompt should include undo and redo hints if engine returns true") { + val in = new ByteArrayInputStream("\nq\n".getBytes) + val out = new ByteArrayOutputStream() + + val engine = new GameEngine() { + // Stub engine to force undo/redo to true + override def canUndo: Boolean = true + override def canRedo: Boolean = true + } + + val ui = new TerminalUI(engine) + + Console.withIn(in) { + Console.withOut(out) { + ui.start() + } + } + + val output = out.toString + output should include("[undo]") + output should include("[redo]") + } + + test("TerminalUI onGameEvent should properly format InvalidMoveEvent") { + val out = new ByteArrayOutputStream() + val engine = new GameEngine() + val ui = new TerminalUI(engine) + + Console.withOut(out) { + ui.onGameEvent(InvalidMoveEvent(Board(Map.empty), GameHistory(), Color.Black, "Invalid move format")) + } + + out.toString should include("⚠️") + out.toString should include("Invalid move format") + } + + test("TerminalUI onGameEvent should properly format CheckDetectedEvent") { + val out = new ByteArrayOutputStream() + val engine = new GameEngine() + val ui = new TerminalUI(engine) + + Console.withOut(out) { + ui.onGameEvent(CheckDetectedEvent(Board(Map.empty), GameHistory(), Color.Black)) + } + + out.toString should include("Black is in check!") + } + + test("TerminalUI onGameEvent should properly format CheckmateEvent") { + val out = new ByteArrayOutputStream() + val engine = new GameEngine() + val ui = new TerminalUI(engine) + + Console.withOut(out) { + ui.onGameEvent(CheckmateEvent(Board(Map.empty), GameHistory(), Color.Black, Color.White)) + } + + val ostr = out.toString + ostr should include("Checkmate! White wins.") + } + + test("TerminalUI onGameEvent should properly format StalemateEvent") { + val out = new ByteArrayOutputStream() + val engine = new GameEngine() + val ui = new TerminalUI(engine) + + Console.withOut(out) { + ui.onGameEvent(StalemateEvent(Board(Map.empty), GameHistory(), Color.Black)) + } + + out.toString should include("Stalemate! The game is a draw.") + } + + test("TerminalUI onGameEvent should properly format BoardResetEvent") { + val out = new ByteArrayOutputStream() + val engine = new GameEngine() + val ui = new TerminalUI(engine) + + Console.withOut(out) { + ui.onGameEvent(BoardResetEvent(Board(Map.empty), GameHistory(), Color.White)) + } + + out.toString should include("Board has been reset to initial position.") + } + + test("TerminalUI onGameEvent should properly format MoveExecutedEvent with capturing piece") { + val out = new ByteArrayOutputStream() + val engine = new GameEngine() + val ui = new TerminalUI(engine) + + Console.withOut(out) { + ui.onGameEvent(MoveExecutedEvent(Board(Map.empty), GameHistory(), Color.Black, "A1", "A8", Some("Knight(White)"))) + } + + out.toString should include("Captured: Knight(White) on A8") // Depending on how piece/coord serialize + } +}