Files
aurorabot/shared/games/chess/plugin.test.ts
syntaxbullet 0dadc82f84
Some checks failed
Deploy to Production / test (push) Failing after 39s
feat(chess): replace custom engine with chess.js and react-chessboard
Swap the custom move validation and Unicode piece grid for chess.js
(full rules engine with check/checkmate/castling/en passant/promotion)
and react-chessboard (drag-and-drop SVG board). Board styled to match
the purple dark theme and auto-orients to the player's color.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 14:22:27 +02:00

130 lines
5.6 KiB
TypeScript

import { describe, it, expect } from "bun:test";
import { chessPlugin } from "./plugin";
import { gameRegistry } from "../registry";
const PLAYER_WHITE = "player1";
const PLAYER_BLACK = "player2";
describe("chessPlugin", () => {
describe("metadata", () => {
it("should have correct slug and player counts", () => {
expect(chessPlugin.slug).toBe("chess");
expect(chessPlugin.minPlayers).toBe(2);
expect(chessPlugin.maxPlayers).toBe(2);
});
});
describe("createInitialState", () => {
it("should create initial FEN position", () => {
const state = chessPlugin.createInitialState([PLAYER_WHITE, PLAYER_BLACK]);
expect(state.fen).toBe("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1");
expect(state.players.white).toBe(PLAYER_WHITE);
expect(state.players.black).toBe(PLAYER_BLACK);
expect(state.moveHistory).toEqual([]);
expect(state.status).toBe("playing");
});
});
describe("handleAction — move", () => {
it("should allow a legal pawn move", () => {
const state = chessPlugin.createInitialState([PLAYER_WHITE, PLAYER_BLACK]);
const result = chessPlugin.handleAction(state, { type: "move", from: "e2", to: "e4" }, PLAYER_WHITE);
expect(result.ok).toBe(true);
if (result.ok) {
expect(result.state.fen).not.toBe(state.fen);
expect(result.state.moveHistory).toEqual(["e4"]);
}
});
it("should reject move when it is not the player's turn", () => {
const state = chessPlugin.createInitialState([PLAYER_WHITE, PLAYER_BLACK]);
const result = chessPlugin.handleAction(state, { type: "move", from: "e7", to: "e5" }, PLAYER_BLACK);
expect(result.ok).toBe(false);
});
it("should reject an illegal move", () => {
const state = chessPlugin.createInitialState([PLAYER_WHITE, PLAYER_BLACK]);
const result = chessPlugin.handleAction(state, { type: "move", from: "e2", to: "e5" }, PLAYER_WHITE);
expect(result.ok).toBe(false);
});
it("should reject a non-player's move", () => {
const state = chessPlugin.createInitialState([PLAYER_WHITE, PLAYER_BLACK]);
const result = chessPlugin.handleAction(state, { type: "move", from: "e2", to: "e4" }, "random_player");
expect(result.ok).toBe(false);
});
it("should detect checkmate", () => {
// Scholar's mate: 1.e4 e5 2.Bc4 Nc6 3.Qh5 Nf6 4.Qxf7#
let state = chessPlugin.createInitialState([PLAYER_WHITE, PLAYER_BLACK]);
const moves = [
{ player: PLAYER_WHITE, from: "e2", to: "e4" },
{ player: PLAYER_BLACK, from: "e7", to: "e5" },
{ player: PLAYER_WHITE, from: "f1", to: "c4" },
{ player: PLAYER_BLACK, from: "b8", to: "c6" },
{ player: PLAYER_WHITE, from: "d1", to: "h5" },
{ player: PLAYER_BLACK, from: "g8", to: "f6" },
{ player: PLAYER_WHITE, from: "h5", to: "f7" },
];
for (const m of moves) {
const result = chessPlugin.handleAction(state, { type: "move", from: m.from, to: m.to }, m.player);
expect(result.ok).toBe(true);
if (result.ok) state = result.state;
}
expect(state.status).toBe("checkmate");
expect(state.winner).toBe(PLAYER_WHITE);
});
});
describe("handleAction — forfeit", () => {
it("should end the game with the other player as winner", () => {
const state = chessPlugin.createInitialState([PLAYER_WHITE, PLAYER_BLACK]);
const result = chessPlugin.handleAction(state, { type: "forfeit" }, PLAYER_WHITE);
expect(result.ok).toBe(true);
if (result.ok) {
expect(result.state.status).toBe("forfeit");
expect(result.state.winner).toBe(PLAYER_BLACK);
}
});
});
describe("getPlayerView", () => {
it("should return full state", () => {
const state = chessPlugin.createInitialState([PLAYER_WHITE, PLAYER_BLACK]);
const view = chessPlugin.getPlayerView(state, PLAYER_WHITE);
expect(view).toEqual(state);
});
});
describe("isGameOver", () => {
it("should return null for ongoing game", () => {
const state = chessPlugin.createInitialState([PLAYER_WHITE, PLAYER_BLACK]);
expect(chessPlugin.isGameOver!(state)).toBeNull();
});
it("should return winner for forfeit", () => {
const state = chessPlugin.createInitialState([PLAYER_WHITE, PLAYER_BLACK]);
state.status = "forfeit";
state.winner = PLAYER_BLACK;
expect(chessPlugin.isGameOver!(state)).toEqual({ winner: PLAYER_BLACK, reason: "forfeit" });
});
});
describe("onPlayerDisconnect", () => {
it("should forfeit the disconnected player", () => {
const state = chessPlugin.createInitialState([PLAYER_WHITE, PLAYER_BLACK]);
const result = chessPlugin.onPlayerDisconnect!(state, PLAYER_WHITE);
expect(result.status).toBe("forfeit");
expect(result.winner).toBe(PLAYER_BLACK);
});
});
describe("chess registration", () => {
it("should register and retrieve from gameRegistry", () => {
gameRegistry.register(chessPlugin);
expect(gameRegistry.get("chess")).toBe(chessPlugin);
expect(gameRegistry.list()).toContain(chessPlugin);
});
});
});