import { render, screen, fireEvent, waitFor, act } from "@testing-library/react"; import ChessGame from "./ChessGame"; import { Tutor } from "./Tutor"; interface MockChessboardProps { options: { onPieceDrop?: (move: { sourceSquare: string; targetSquare: string | null }) => void; }; } interface MockStartOptions { personality: { name: string }; color: 'white' | 'black' | 'random'; } // Mock dependencies jest.mock("react-chessboard", () => ({ Chessboard: ({ options }: MockChessboardProps) => (
{ // Simulate a move drop if (options.onPieceDrop) { options.onPieceDrop({ sourceSquare: "e2", targetSquare: "e4" }); } }}> Chessboard Mock
), })); jest.mock("../lib/stockfish", () => { const evaluate = jest.fn().mockResolvedValue({ score: 0.5, mate: null, bestMove: "e7e5", depth: 15 }); return { __mock: { evaluate }, Stockfish: jest.fn().mockImplementation(() => ({ evaluate, terminate: jest.fn(), })), }; }); const { __mock: stockfishMock } = jest.requireMock("../lib/stockfish") as { __mock: { evaluate: jest.Mock } }; jest.mock("./Tutor", () => ({ Tutor: jest.fn(({ currentFen, userMove, computerMove, evalP0, evalP2, openingData, language }) => (
Tutor Mock (Fen: {currentFen}) {userMove && User Move: {userMove.san}} {computerMove && Computer Move: {computerMove.san}} {evalP0 && Eval P0: {evalP0.score}} {evalP2 && Eval P2: {evalP2.score}} {openingData && Opening: {openingData.name}} Language: {language}
)), })); jest.mock("./GameAnalysisModal", () => ({ GameAnalysisModal: () =>
Analysis Modal Mock
, })); jest.mock("./GameOverModal", () => ({ GameOverModal: ({ onAnalyze }: { onAnalyze: () => void }) => (
Game Over Modal Mock
), })); jest.mock("./StartScreen", () => ({ __esModule: true, default: ({ onStartGame }: { onStartGame: (options: MockStartOptions) => void }) => (
), })); describe("ChessGame Component", () => { const mockPersonality = { id: "test", name: "Test Personality", systemPrompt: "You are a helpful assistant.", image: "🤖", description: "Test description", }; beforeEach(() => { localStorage.clear(); jest.clearAllMocks(); jest.useFakeTimers(); stockfishMock.evaluate.mockResolvedValue({ score: 0.5, mate: null, bestMove: "e7e5", depth: 15 }); }); it("renders the game board and tutor", async () => { await act(async () => { render( {}} /> ); }); expect(screen.getByTestId("chessboard")).toBeInTheDocument(); expect(screen.getByTestId("tutor")).toBeInTheDocument(); }); it("handles user move and triggers analysis", async () => { render( {}} /> ); const mockedTutor = jest.mocked(Tutor); const initialCalls = mockedTutor.mock.calls.length; // Make a move by clicking the mock chessboard await act(async () => { fireEvent.click(screen.getByTestId("chessboard")); jest.runAllTimers(); }); // Wait for the component to update await waitFor(() => { expect(mockedTutor.mock.calls.length).toBeGreaterThan(initialCalls); }); }); it("restores a PGN game and persists save data without apiKey", async () => { await act(async () => { render( {}} /> ); }); await waitFor(() => { const saved = JSON.parse(localStorage.getItem("chess_tutor_save") || "{}"); expect(saved.id).toBe("restore-game"); expect(saved.pgn).toContain("1. e4 e5"); expect(saved).not.toHaveProperty("apiKey"); }); }); it("undoes cleanly while analysis is in flight", async () => { render( {}} /> ); await act(async () => { fireEvent.click(screen.getByTestId("chessboard")); }); await act(async () => { fireEvent.click(screen.getByText(/undo/i)); jest.runAllTimers(); }); await waitFor(() => { const tutor = screen.getByTestId("tutor"); expect(tutor).not.toHaveTextContent("Computer Move:"); }); }); it("ignores rapid repeated drops once the turn has switched", async () => { render( {}} /> ); await waitFor(() => { expect(stockfishMock.evaluate).toHaveBeenCalled(); }); await waitFor(() => { expect(screen.getByTestId("tutor")).toHaveTextContent("Eval P0: 0.5"); }); stockfishMock.evaluate.mockClear(); await act(async () => { fireEvent.click(screen.getByTestId("chessboard")); fireEvent.click(screen.getByTestId("chessboard")); jest.runAllTimers(); }); await waitFor(() => { expect(stockfishMock.evaluate.mock.calls.length).toBeGreaterThanOrEqual(1); }); const playerTriggeredEvaluations = stockfishMock.evaluate.mock.calls.filter(([fen]: [string]) => typeof fen === "string"); expect(playerTriggeredEvaluations.length).toBeLessThanOrEqual(2); }); });