refactor(tests): enhance test coverage for move application and piece movement logic
This commit is contained in:
@@ -0,0 +1,101 @@
|
||||
package de.nowchess.io
|
||||
|
||||
import de.nowchess.api.board.{Board, Color, File, Piece, PieceType, Rank, Square}
|
||||
import de.nowchess.api.game.GameContext
|
||||
import de.nowchess.api.move.{Move, MoveType, PromotionPiece}
|
||||
import de.nowchess.io.fen.{FenExporter, FenParser}
|
||||
import de.nowchess.io.pgn.{PgnExporter, PgnParser}
|
||||
import org.scalatest.funsuite.AnyFunSuite
|
||||
import org.scalatest.matchers.should.Matchers
|
||||
|
||||
class IoCoverageRegressionTest extends AnyFunSuite with Matchers:
|
||||
|
||||
private def sq(alg: String): Square =
|
||||
Square.fromAlgebraic(alg).getOrElse(fail(s"Invalid square in test: $alg"))
|
||||
|
||||
test("FenParser rejects malformed board shapes and invalid piece symbols"):
|
||||
FenParser.parseBoard("8/8/8/8/8/8/8") shouldBe None
|
||||
FenParser.parseBoard("9/8/8/8/8/8/8/8") shouldBe None
|
||||
FenParser.parseBoard("8p/8/8/8/8/8/8/8") shouldBe None
|
||||
FenParser.parseBoard("7/8/8/8/8/8/8/8") shouldBe None
|
||||
FenParser.parseBoard("8/8/8/8/8/8/8/7X") shouldBe None
|
||||
|
||||
test("FenExporter exportGameContext forwards to gameContextToFen"):
|
||||
val context = GameContext.initial
|
||||
|
||||
FenExporter.exportGameContext(context) shouldBe FenExporter.gameContextToFen(context)
|
||||
|
||||
test("PgnParser rejects too-short notation and invalid piece letters"):
|
||||
val initial = GameContext.initial
|
||||
|
||||
PgnParser.parseAlgebraicMove("e", initial, Color.White) shouldBe None
|
||||
PgnParser.parseAlgebraicMove("Xe5", initial, Color.White) shouldBe None
|
||||
|
||||
test("PgnParser rejects notation with invalid promotion piece"):
|
||||
val board = FenParser.parseBoard("8/4P3/4k3/8/8/8/8/8").getOrElse(fail("valid board expected"))
|
||||
val context = GameContext.initial.withBoard(board)
|
||||
|
||||
PgnParser.parseAlgebraicMove("e7e8=X", context, Color.White) shouldBe None
|
||||
|
||||
test("PgnExporter emits notation for all normal piece types and captures"):
|
||||
val moves = List(
|
||||
Move(sq("e2"), sq("e4")),
|
||||
Move(sq("a7"), sq("a6")),
|
||||
Move(sq("g1"), sq("f3")),
|
||||
Move(sq("b7"), sq("b6")),
|
||||
Move(sq("f1"), sq("b5"), MoveType.Normal(true)),
|
||||
Move(sq("g8"), sq("f6")),
|
||||
Move(sq("a1"), sq("a8"), MoveType.Normal(true)),
|
||||
Move(sq("c7"), sq("c6")),
|
||||
Move(sq("d1"), sq("d7"), MoveType.Normal(true)),
|
||||
Move(sq("d8"), sq("d7"), MoveType.Normal(true)),
|
||||
Move(sq("e1"), sq("e2"), MoveType.Normal(true))
|
||||
)
|
||||
|
||||
val pgn = PgnExporter.exportGame(Map("Result" -> "*"), moves)
|
||||
|
||||
pgn should include("e4")
|
||||
pgn should include("Nf3")
|
||||
pgn should include("Bxb5")
|
||||
pgn should include("Rxa8")
|
||||
pgn should include("Qxd7")
|
||||
pgn should include("Kxe2")
|
||||
|
||||
test("PgnExporter emits en-passant and promotion capture notation"):
|
||||
val enPassant = Move(sq("e2"), sq("d3"), MoveType.EnPassant)
|
||||
val promotionCapture = Move(sq("e7"), sq("f8"), MoveType.Promotion(PromotionPiece.Queen))
|
||||
val pawnCapture = Move(sq("e2"), sq("d3"), MoveType.Normal(isCapture = true))
|
||||
val promotionQuietSetup = Move(sq("e8"), sq("e7"))
|
||||
val promotionQuiet = Move(sq("e2"), sq("e8"), MoveType.Promotion(PromotionPiece.Queen))
|
||||
|
||||
val pgn = PgnExporter.exportGame(Map.empty, List(enPassant, promotionCapture))
|
||||
val pawnCapturePgn = PgnExporter.exportGame(Map.empty, List(pawnCapture))
|
||||
val quietPromotionPgn = PgnExporter.exportGame(Map.empty, List(promotionQuietSetup, promotionQuiet))
|
||||
|
||||
pgn should include("exd3")
|
||||
pgn should include("exf8=Q")
|
||||
pawnCapturePgn should include("exd3")
|
||||
quietPromotionPgn should include("e8=Q")
|
||||
|
||||
test("PgnExporter emits all promotion suffixes"):
|
||||
val promotions = List(
|
||||
Move(sq("e2"), sq("e1"), MoveType.Promotion(PromotionPiece.Queen)),
|
||||
Move(sq("e2"), sq("e1"), MoveType.Promotion(PromotionPiece.Rook)),
|
||||
Move(sq("e2"), sq("e1"), MoveType.Promotion(PromotionPiece.Bishop)),
|
||||
Move(sq("e2"), sq("e1"), MoveType.Promotion(PromotionPiece.Knight))
|
||||
)
|
||||
|
||||
val pgn = PgnExporter.exportGame(Map.empty, promotions)
|
||||
|
||||
pgn should include("=Q")
|
||||
pgn should include("=R")
|
||||
pgn should include("=B")
|
||||
pgn should include("=N")
|
||||
|
||||
test("PgnParser parsePgn silently skips unknown tokens"):
|
||||
val parsed = PgnParser.parsePgn("1. e4 ??? e5")
|
||||
|
||||
parsed.map(_.moves.size) shouldBe Some(2)
|
||||
|
||||
|
||||
|
||||
@@ -167,3 +167,28 @@ class PgnParserTest extends AnyFunSuite with Matchers:
|
||||
val result = PgnParser.importGameContext(invalidPgn)
|
||||
// Empty PGN is still valid (no moves), so check for reasonable parsing
|
||||
result.isRight shouldBe true
|
||||
|
||||
test("parseAlgebraicMove: uppercase file token still fails when destination is unreachable"):
|
||||
val result = PgnParser.parseAlgebraicMove("E5", GameContext.initial, Color.White)
|
||||
result shouldBe None
|
||||
|
||||
test("parseAlgebraicMove: non-file/rank hint characters are ignored"):
|
||||
val result = PgnParser.parseAlgebraicMove("N?f3", GameContext.initial, Color.White)
|
||||
result.isDefined shouldBe true
|
||||
result.get.to shouldBe Square(File.F, Rank.R3)
|
||||
|
||||
test("extractPromotion returns None for unsupported promotion letter"):
|
||||
PgnParser.extractPromotion("e7e8=X") shouldBe None
|
||||
|
||||
test("parseAlgebraicMove rejects promotion target without promotion suffix"):
|
||||
val board = FenParser.parseBoard("8/4P3/4k3/8/8/8/8/8").get
|
||||
val result = PgnParser.parseAlgebraicMove("e8", GameContext.initial.withBoard(board), Color.White)
|
||||
result shouldBe None
|
||||
|
||||
test("parseAlgebraicMove: king notation resolves a legal king move"):
|
||||
val board = FenParser.parseBoard("4k3/8/8/8/8/8/8/4K3").get
|
||||
val result = PgnParser.parseAlgebraicMove("Ke2", GameContext.initial.withBoard(board), Color.White)
|
||||
result.isDefined shouldBe true
|
||||
result.get.from shouldBe Square(File.E, Rank.R1)
|
||||
result.get.to shouldBe Square(File.E, Rank.R2)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user