refactor(tests): streamline test cases for ApiResponse, Board, GameContext, Piece, and Square
This commit is contained in:
@@ -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)
|
||||
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 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})')
|
||||
if m2:s+=int(m2.group(1))
|
||||
print(f'{m}: {s}')
|
||||
tot+=s
|
||||
print('overall:',tot)
|
||||
@@ -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") {
|
||||
|
||||
@@ -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)
|
||||
expected.foreach { case (actual, wanted) =>
|
||||
actual shouldBe wanted
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
-6
@@ -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()
|
||||
|
||||
@@ -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[")
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user