diff --git a/jacoco-reporter/test_counter.py b/jacoco-reporter/test_counter.py index a32fa3f..bd13edc 100644 --- a/jacoco-reporter/test_counter.py +++ b/jacoco-reporter/test_counter.py @@ -1,10 +1,12 @@ -import glob,re,os -rows=[] -for f in glob.glob('modules/*/build/test-results/test/TEST-*.xml'): - txt=open(f,encoding='utf-8').read(500) - m1=re.search(r'name="([^"]+)"',txt) - m2=re.search(r'tests="(\d+)"',txt) - if m1 and m2: - rows.append((int(m2.group(1)),f,m1.group(1))) -for n,f,name in sorted(rows, reverse=True)[:20]: - print(f'{n:3} {name} ({f})') \ No newline at end of file +import glob,re +mods=['api','core','io','rule','ui'] +tot=0 +for m in mods: + s=0 + for f in glob.glob(f'modules/{m}/build/test-results/test/TEST-*.xml'): + txt=open(f,encoding='utf-8').read(300) + m2=re.search(r'tests="(\d+)"',txt) + if m2:s+=int(m2.group(1)) + print(f'{m}: {s}') + tot+=s +print('overall:',tot) \ No newline at end of file diff --git a/modules/api/src/test/scala/de/nowchess/api/board/BoardTest.scala b/modules/api/src/test/scala/de/nowchess/api/board/BoardTest.scala index a42dcd4..ae3b4d8 100644 --- a/modules/api/src/test/scala/de/nowchess/api/board/BoardTest.scala +++ b/modules/api/src/test/scala/de/nowchess/api/board/BoardTest.scala @@ -8,13 +8,9 @@ class BoardTest extends AnyFunSuite with Matchers: private val e2 = Square(File.E, Rank.R2) private val e4 = Square(File.E, Rank.R4) - private val d7 = Square(File.D, Rank.R7) - test("pieceAt returns Some for occupied square") { + test("pieceAt resolves occupied and empty squares") { Board.initial.pieceAt(e2) shouldBe Some(Piece.WhitePawn) - } - - test("pieceAt returns None for empty square") { Board.initial.pieceAt(e4) shouldBe None } @@ -35,38 +31,20 @@ class BoardTest extends AnyFunSuite with Matchers: board.pieceAt(from) shouldBe None } - test("pieces returns the underlying map") { - val map = Map(e2 -> Piece.WhitePawn) - val b = Board(map) - b.pieces shouldBe map - } - - test("Board.apply constructs board from map") { + test("Board.apply and pieces expose the wrapped map") { val map = Map(e2 -> Piece.WhitePawn) val b = Board(map) b.pieceAt(e2) shouldBe Some(Piece.WhitePawn) + b.pieces shouldBe map } - test("initial board has 32 pieces") { + test("initial board has expected material and pawn placement") { Board.initial.pieces should have size 32 - } - - test("initial board has 16 white pieces") { Board.initial.pieces.values.count(_.color == Color.White) shouldBe 16 - } - - test("initial board has 16 black pieces") { Board.initial.pieces.values.count(_.color == Color.Black) shouldBe 16 - } - test("initial board white pawns on rank 2") { File.values.foreach { file => Board.initial.pieceAt(Square(file, Rank.R2)) shouldBe Some(Piece.WhitePawn) - } - } - - test("initial board black pawns on rank 7") { - File.values.foreach { file => Board.initial.pieceAt(Square(file, Rank.R7)) shouldBe Some(Piece.BlackPawn) } } @@ -102,17 +80,14 @@ class BoardTest extends AnyFunSuite with Matchers: Board.initial.pieceAt(Square(file, rank)) shouldBe None } - test("updated adds or replaces piece at square") { + test("updated adds and replaces piece at squares") { val b = Board(Map(e2 -> Piece.WhitePawn)) - val updated = b.updated(e4, Piece.WhiteKnight) - updated.pieceAt(e2) shouldBe Some(Piece.WhitePawn) - updated.pieceAt(e4) shouldBe Some(Piece.WhiteKnight) - } + val added = b.updated(e4, Piece.WhiteKnight) + added.pieceAt(e2) shouldBe Some(Piece.WhitePawn) + added.pieceAt(e4) shouldBe Some(Piece.WhiteKnight) - test("updated replaces existing piece") { - val b = Board(Map(e2 -> Piece.WhitePawn)) - val updated = b.updated(e2, Piece.WhiteKnight) - updated.pieceAt(e2) shouldBe Some(Piece.WhiteKnight) + val replaced = b.updated(e2, Piece.WhiteKnight) + replaced.pieceAt(e2) shouldBe Some(Piece.WhiteKnight) } test("removed deletes piece from board") { diff --git a/modules/api/src/test/scala/de/nowchess/api/board/PieceTest.scala b/modules/api/src/test/scala/de/nowchess/api/board/PieceTest.scala index 850bdca..9628b01 100644 --- a/modules/api/src/test/scala/de/nowchess/api/board/PieceTest.scala +++ b/modules/api/src/test/scala/de/nowchess/api/board/PieceTest.scala @@ -11,50 +11,23 @@ class PieceTest extends AnyFunSuite with Matchers: p.pieceType shouldBe PieceType.Queen } - test("WhitePawn convenience constant") { - Piece.WhitePawn shouldBe Piece(Color.White, PieceType.Pawn) - } + test("all convenience constants map to expected color and piece type") { + val expected = List( + Piece.WhitePawn -> Piece(Color.White, PieceType.Pawn), + Piece.WhiteKnight -> Piece(Color.White, PieceType.Knight), + Piece.WhiteBishop -> Piece(Color.White, PieceType.Bishop), + Piece.WhiteRook -> Piece(Color.White, PieceType.Rook), + Piece.WhiteQueen -> Piece(Color.White, PieceType.Queen), + Piece.WhiteKing -> Piece(Color.White, PieceType.King), + Piece.BlackPawn -> Piece(Color.Black, PieceType.Pawn), + Piece.BlackKnight -> Piece(Color.Black, PieceType.Knight), + Piece.BlackBishop -> Piece(Color.Black, PieceType.Bishop), + Piece.BlackRook -> Piece(Color.Black, PieceType.Rook), + Piece.BlackQueen -> Piece(Color.Black, PieceType.Queen), + Piece.BlackKing -> Piece(Color.Black, PieceType.King) + ) - test("WhiteKnight convenience constant") { - Piece.WhiteKnight shouldBe Piece(Color.White, PieceType.Knight) - } - - test("WhiteBishop convenience constant") { - Piece.WhiteBishop shouldBe Piece(Color.White, PieceType.Bishop) - } - - test("WhiteRook convenience constant") { - Piece.WhiteRook shouldBe Piece(Color.White, PieceType.Rook) - } - - test("WhiteQueen convenience constant") { - Piece.WhiteQueen shouldBe Piece(Color.White, PieceType.Queen) - } - - test("WhiteKing convenience constant") { - Piece.WhiteKing shouldBe Piece(Color.White, PieceType.King) - } - - test("BlackPawn convenience constant") { - Piece.BlackPawn shouldBe Piece(Color.Black, PieceType.Pawn) - } - - test("BlackKnight convenience constant") { - Piece.BlackKnight shouldBe Piece(Color.Black, PieceType.Knight) - } - - test("BlackBishop convenience constant") { - Piece.BlackBishop shouldBe Piece(Color.Black, PieceType.Bishop) - } - - test("BlackRook convenience constant") { - Piece.BlackRook shouldBe Piece(Color.Black, PieceType.Rook) - } - - test("BlackQueen convenience constant") { - Piece.BlackQueen shouldBe Piece(Color.Black, PieceType.Queen) - } - - test("BlackKing convenience constant") { - Piece.BlackKing shouldBe Piece(Color.Black, PieceType.King) + expected.foreach { case (actual, wanted) => + actual shouldBe wanted + } } diff --git a/modules/api/src/test/scala/de/nowchess/api/board/SquareTest.scala b/modules/api/src/test/scala/de/nowchess/api/board/SquareTest.scala index 6806433..c294f0f 100644 --- a/modules/api/src/test/scala/de/nowchess/api/board/SquareTest.scala +++ b/modules/api/src/test/scala/de/nowchess/api/board/SquareTest.scala @@ -5,67 +5,32 @@ import org.scalatest.matchers.should.Matchers class SquareTest extends AnyFunSuite with Matchers: - test("Square.toString produces lowercase file and rank number") { - Square(File.E, Rank.R4).toString shouldBe "e4" - } - - test("Square.toString for a1") { + test("toString renders algebraic notation for edge and middle squares") { Square(File.A, Rank.R1).toString shouldBe "a1" - } - - test("Square.toString for h8") { + Square(File.E, Rank.R4).toString shouldBe "e4" Square(File.H, Rank.R8).toString shouldBe "h8" } - test("fromAlgebraic parses valid square e4") { - Square.fromAlgebraic("e4") shouldBe Some(Square(File.E, Rank.R4)) + test("fromAlgebraic parses valid coordinates including case-insensitive files") { + val expected = List( + "a1" -> Square(File.A, Rank.R1), + "e4" -> Square(File.E, Rank.R4), + "h8" -> Square(File.H, Rank.R8), + "E4" -> Square(File.E, Rank.R4) + ) + expected.foreach { case (raw, sq) => + Square.fromAlgebraic(raw) shouldBe Some(sq) + } } - test("fromAlgebraic parses valid square a1") { - Square.fromAlgebraic("a1") shouldBe Some(Square(File.A, Rank.R1)) + test("fromAlgebraic rejects malformed coordinates") { + List("", "e", "e42", "z4", "ex", "e0", "e9").foreach { raw => + Square.fromAlgebraic(raw) shouldBe None + } } - test("fromAlgebraic parses valid square h8") { - Square.fromAlgebraic("h8") shouldBe Some(Square(File.H, Rank.R8)) - } - - test("fromAlgebraic is case-insensitive for file") { - Square.fromAlgebraic("E4") shouldBe Some(Square(File.E, Rank.R4)) - } - - test("fromAlgebraic returns None for empty string") { - Square.fromAlgebraic("") shouldBe None - } - - test("fromAlgebraic returns None for string too short") { - Square.fromAlgebraic("e") shouldBe None - } - - test("fromAlgebraic returns None for string too long") { - Square.fromAlgebraic("e42") shouldBe None - } - - test("fromAlgebraic returns None for invalid file character") { - Square.fromAlgebraic("z4") shouldBe None - } - - test("fromAlgebraic returns None for non-digit rank") { - Square.fromAlgebraic("ex") shouldBe None - } - - test("fromAlgebraic returns None for rank 0") { - Square.fromAlgebraic("e0") shouldBe None - } - - test("fromAlgebraic returns None for rank 9") { - Square.fromAlgebraic("e9") shouldBe None - } - - test("offset returns target square for in-bounds delta") { + test("offset returns Some in-bounds and None out-of-bounds") { Square(File.E, Rank.R4).offset(1, 2) shouldBe Some(Square(File.F, Rank.R6)) - } - - test("offset returns None for out-of-bounds delta") { Square(File.A, Rank.R1).offset(-1, 0) shouldBe None Square(File.H, Rank.R8).offset(0, 1) shouldBe None } diff --git a/modules/api/src/test/scala/de/nowchess/api/game/GameContextTest.scala b/modules/api/src/test/scala/de/nowchess/api/game/GameContextTest.scala index 685ce27..3ad4f34 100644 --- a/modules/api/src/test/scala/de/nowchess/api/game/GameContextTest.scala +++ b/modules/api/src/test/scala/de/nowchess/api/game/GameContextTest.scala @@ -7,23 +7,15 @@ import org.scalatest.matchers.should.Matchers class GameContextTest extends AnyFunSuite with Matchers: - test("GameContext.initial has initial board"): - GameContext.initial.board shouldBe Board.initial + test("GameContext.initial exposes expected default state"): + val initial = GameContext.initial - test("GameContext.initial active color is White"): - GameContext.initial.turn shouldBe Color.White - - test("GameContext.initial has full castling rights"): - GameContext.initial.castlingRights shouldBe CastlingRights.Initial - - test("GameContext.initial en-passant square is None"): - GameContext.initial.enPassantSquare shouldBe None - - test("GameContext.initial half-move clock is 0"): - GameContext.initial.halfMoveClock shouldBe 0 - - test("GameContext.initial move history is empty"): - GameContext.initial.moves shouldBe List.empty + initial.board shouldBe Board.initial + initial.turn shouldBe Color.White + initial.castlingRights shouldBe CastlingRights.Initial + initial.enPassantSquare shouldBe None + initial.halfMoveClock shouldBe 0 + initial.moves shouldBe List.empty test("withBoard updates only board"): val square = Square(File.E, Rank.R4) @@ -36,26 +28,31 @@ class GameContextTest extends AnyFunSuite with Matchers: updated.halfMoveClock shouldBe GameContext.initial.halfMoveClock updated.moves shouldBe GameContext.initial.moves - test("withTurn updates only turn"): - val updated = GameContext.initial.withTurn(Color.Black) - updated.turn shouldBe Color.Black - updated.board shouldBe GameContext.initial.board - - test("withCastlingRights updates castling rights"): + test("withers update only targeted fields"): + val initial = GameContext.initial val rights = CastlingRights( whiteKingSide = true, whiteQueenSide = false, blackKingSide = false, blackQueenSide = true ) - GameContext.initial.withCastlingRights(rights).castlingRights shouldBe rights - - test("withEnPassantSquare updates en-passant square"): val square = Some(Square(File.E, Rank.R3)) - GameContext.initial.withEnPassantSquare(square).enPassantSquare shouldBe square + val updatedTurn = initial.withTurn(Color.Black) + val updatedRights = initial.withCastlingRights(rights) + val updatedEp = initial.withEnPassantSquare(square) + val updatedClock = initial.withHalfMoveClock(17) - test("withHalfMoveClock updates half-move clock"): - GameContext.initial.withHalfMoveClock(17).halfMoveClock shouldBe 17 + updatedTurn.turn shouldBe Color.Black + updatedTurn.board shouldBe initial.board + + updatedRights.castlingRights shouldBe rights + updatedRights.turn shouldBe initial.turn + + updatedEp.enPassantSquare shouldBe square + updatedEp.castlingRights shouldBe initial.castlingRights + + updatedClock.halfMoveClock shouldBe 17 + updatedClock.moves shouldBe initial.moves test("withMove appends move to history"): val move = Move(Square(File.E, Rank.R2), Square(File.E, Rank.R4)) diff --git a/modules/api/src/test/scala/de/nowchess/api/response/ApiResponseTest.scala b/modules/api/src/test/scala/de/nowchess/api/response/ApiResponseTest.scala index 44d43ef..4f52147 100644 --- a/modules/api/src/test/scala/de/nowchess/api/response/ApiResponseTest.scala +++ b/modules/api/src/test/scala/de/nowchess/api/response/ApiResponseTest.scala @@ -5,52 +5,26 @@ import org.scalatest.matchers.should.Matchers class ApiResponseTest extends AnyFunSuite with Matchers: - test("ApiResponse.Success carries data") { + test("ApiResponse factories and payload wrappers keep values") { val r = ApiResponse.Success(42) r.data shouldBe 42 - } - test("ApiResponse.Failure carries error list") { val err = ApiError("CODE", "msg") - val r = ApiResponse.Failure(List(err)) - r.errors shouldBe List(err) - } + ApiResponse.Failure(List(err)).errors shouldBe List(err) + ApiResponse.error(err) shouldBe ApiResponse.Failure(List(err)) - test("ApiResponse.error creates single-error Failure") { - val err = ApiError("NOT_FOUND", "not found") - val f = ApiResponse.error(err) - f shouldBe ApiResponse.Failure(List(err)) - } - - test("ApiError holds code and message") { val e = ApiError("CODE", "message") e.code shouldBe "CODE" e.message shouldBe "message" e.field shouldBe None + ApiError("INVALID", "bad value", Some("email")).field shouldBe Some("email") } - test("ApiError holds optional field") { - val e = ApiError("INVALID", "bad value", Some("email")) - e.field shouldBe Some("email") - } - - test("Pagination.totalPages with exact division") { + test("Pagination.totalPages handles normal and guarded inputs") { Pagination(page = 0, pageSize = 10, totalItems = 30).totalPages shouldBe 3 - } - - test("Pagination.totalPages rounds up") { Pagination(page = 0, pageSize = 10, totalItems = 25).totalPages shouldBe 3 - } - - test("Pagination.totalPages is 0 when totalItems is 0") { Pagination(page = 0, pageSize = 10, totalItems = 0).totalPages shouldBe 0 - } - - test("Pagination.totalPages is 0 when pageSize is 0") { Pagination(page = 0, pageSize = 0, totalItems = 100).totalPages shouldBe 0 - } - - test("Pagination.totalPages is 0 when pageSize is negative") { Pagination(page = 0, pageSize = -1, totalItems = 100).totalPages shouldBe 0 } diff --git a/modules/core/src/test/scala/de/nowchess/chess/engine/GameEngineCoverageRegressionTest.scala b/modules/core/src/test/scala/de/nowchess/chess/engine/GameEngineCoverageRegressionTest.scala index e17451d..ebc12e8 100644 --- a/modules/core/src/test/scala/de/nowchess/chess/engine/GameEngineCoverageRegressionTest.scala +++ b/modules/core/src/test/scala/de/nowchess/chess/engine/GameEngineCoverageRegressionTest.scala @@ -141,12 +141,6 @@ class GameEngineCoverageRegressionTest extends AnyFunSuite with Matchers: engine.replayMoves(List(normalMove), engine.context) shouldBe Right(()) engine.context.moves.lastOption shouldBe Some(normalMove) - test("loadGame replay will stop on errors"): - val normalMove = Move(sq("e2"), sq("e4"), MoveType.Normal()) - val engine = new GameEngine() - - engine.replayMoves(List(normalMove), engine.context) shouldBe Right(()) - engine.context.moves.lastOption shouldBe Some(normalMove) test("normalMoveNotation handles missing source piece"): val engine = new GameEngine() diff --git a/modules/ui/src/test/scala/de/nowchess/ui/utils/RendererAndUnicodeTest.scala b/modules/ui/src/test/scala/de/nowchess/ui/utils/RendererAndUnicodeTest.scala index 431d6e3..30babf8 100644 --- a/modules/ui/src/test/scala/de/nowchess/ui/utils/RendererAndUnicodeTest.scala +++ b/modules/ui/src/test/scala/de/nowchess/ui/utils/RendererAndUnicodeTest.scala @@ -6,33 +6,22 @@ import org.scalatest.matchers.should.Matchers class RendererAndUnicodeTest extends AnyFunSuite with Matchers: - private val whiteKing = Piece(Color.White, PieceType.King) - private val blackPawn = Piece(Color.Black, PieceType.Pawn) + test("unicode mapping covers representative white and black pieces"): + Piece(Color.White, PieceType.King).unicode shouldBe "\u2654" + Piece(Color.White, PieceType.Queen).unicode shouldBe "\u2655" + Piece(Color.Black, PieceType.King).unicode shouldBe "\u265A" + Piece(Color.Black, PieceType.Pawn).unicode shouldBe "\u265F" - test("unicode returns the correct symbol for white king"): - whiteKing.unicode shouldBe "\u2654" - - test("unicode returns the correct symbol for black pawn"): - blackPawn.unicode shouldBe "\u265F" - - test("render includes board coordinates on top and bottom"): + test("render outputs coordinates ranks ansi escapes and piece glyphs"): + val board = Board(Map(Square(File.E, Rank.R4) -> Piece(Color.White, PieceType.Queen))) val rendered = Renderer.render(Board(Map.empty)) val lines = rendered.trim.split("\\n").toList.map(_.trim) lines.head shouldBe "a b c d e f g h" lines.last shouldBe "a b c d e f g h" - - test("render includes rank labels from 8 down to 1"): - val rendered = Renderer.render(Board(Map.empty)) - rendered should include("8") rendered should include("1") - - test("render places a piece unicode glyph on occupied square"): - val board = Board(Map(Square(File.E, Rank.R4) -> Piece(Color.White, PieceType.Queen))) - val rendered = Renderer.render(board) - - rendered should include("\u2655") - rendered should include("\u001b[") + Renderer.render(board) should include("\u2655") + Renderer.render(board) should include("\u001b[")