Files
restitution/tests/LobbyScreen.test.js
kaykayyali 3fc29f728e feat: Colyseus authoritative server + invite-code lobby
- Replace socket.io relay with Colyseus 0.15 authoritative server
- GameRoom with GameState schema (players, units, resources)
- Pure TS services: CombatResolver, EconomyService, PathfindingService, UnitManager
- POST /api/create-room → 4-char invite code
- React/MUI LobbyScreen: Create (shows code + START GAME) / Join by code
- ColyseusClient: joinOrCreate/join by room type = invite code
- Nginx: static assets direct, all else proxied to Colyseus (WS upgrade)
- Content-hashed JS bundles for Cloudflare cache-busting
- 1-player lobbies: START GAME button bypasses 2-player wait
2026-05-30 02:49:20 +00:00

162 lines
5.0 KiB
JavaScript

/**
* LobbyScreen tests — RED phase
*
* Tests that LobbyScreen renders correctly in all states:
* 1. Initial state: Create Game + Join Game buttons visible
* 2. Create mode: spinner while loading, code shown after creation
* 3. Join mode: text input for 4-char code, Join button disabled until 4 chars
* 4. Error display: Alert shown on error
* 5. Back navigation: from Join mode back to initial
* 6. Player count display after creating
* 7. Auto-advance when 2+ players
*/
import React from "react";
import { render, screen, fireEvent, waitFor, act } from "@testing-library/react";
import "@testing-library/jest-dom";
// We'll import LobbyScreen after it's created — for now, verify the file doesn't exist
// and this test suite will fail with "Cannot find module" (true RED)
import LobbyScreen from "Components/LobbyScreen.jsx";
describe("LobbyScreen", () => {
let mockClient;
beforeEach(() => {
mockClient = {
createGame: jest.fn().mockResolvedValue("ABCD"),
joinGame: jest.fn().mockResolvedValue({}),
onStateChange: jest.fn(),
};
});
afterEach(() => {
jest.clearAllMocks();
});
// --- Test 1: Initial state shows Create and Join buttons ---
it("renders Create Game and Join Game buttons on initial load", () => {
render(
<LobbyScreen colyseusClient={mockClient} onGameStart={jest.fn()} />
);
expect(screen.getByText("Create Game")).toBeInTheDocument();
expect(screen.getByText("Join Game")).toBeInTheDocument();
});
// --- Test 2: Clicking Create Game calls createGame and shows spinner ---
it("calls createGame and shows CircularProgress when Create is clicked", async () => {
render(
<LobbyScreen colyseusClient={mockClient} onGameStart={jest.fn()} />
);
fireEvent.click(screen.getByText("Create Game"));
expect(mockClient.createGame).toHaveBeenCalledTimes(1);
// Spinner should appear while loading
expect(screen.getByRole("progressbar")).toBeInTheDocument();
});
// --- Test 3: After createGame resolves, code is displayed ---
it("displays the invite code and player count after creation", async () => {
render(
<LobbyScreen colyseusClient={mockClient} onGameStart={jest.fn()} />
);
fireEvent.click(screen.getByText("Create Game"));
await waitFor(() => {
expect(screen.getByText("ABCD")).toBeInTheDocument();
});
expect(screen.getByText(/Players:/)).toBeInTheDocument();
});
// --- Test 4: Clicking Join Game shows text input ---
it("shows Join mode with TextField when Join Game is clicked", () => {
render(
<LobbyScreen colyseusClient={mockClient} onGameStart={jest.fn()} />
);
fireEvent.click(screen.getByText("Join Game"));
expect(screen.getByLabelText("Invite Code")).toBeInTheDocument();
expect(screen.getByText("Join")).toBeInTheDocument();
expect(screen.getByText("Back")).toBeInTheDocument();
});
// --- Test 5: Join button is disabled until 4 characters entered ---
it("disables Join button until exactly 4 characters are entered", () => {
render(
<LobbyScreen colyseusClient={mockClient} onGameStart={jest.fn()} />
);
fireEvent.click(screen.getByText("Join Game"));
const joinButton = screen.getByText("Join");
const input = screen.getByLabelText("Invite Code");
// Button disabled with 0 chars
expect(joinButton).toBeDisabled();
// Button disabled with 3 chars
fireEvent.change(input, { target: { value: "ABC" } });
expect(joinButton).toBeDisabled();
// Button enabled with exactly 4 chars
fireEvent.change(input, { target: { value: "ABCD" } });
expect(joinButton).not.toBeDisabled();
});
// --- Test 6: Error displayed when join fails ---
it("shows Alert error when joinGame rejects", async () => {
mockClient.joinGame.mockRejectedValue(new Error());
render(
<LobbyScreen colyseusClient={mockClient} onGameStart={jest.fn()} />
);
fireEvent.click(screen.getByText("Join Game"));
const input = screen.getByLabelText("Invite Code");
fireEvent.change(input, { target: { value: "WXYZ" } });
fireEvent.click(screen.getByText("Join"));
await waitFor(() => {
expect(screen.getByRole("alert")).toBeInTheDocument();
});
expect(screen.getByRole("alert")).toHaveTextContent(/Room not found or full/);
});
// --- Test 7: Auto-advance when 2+ players connect ---
it("calls onGameStart when 2+ players connect after creating", async () => {
const onGameStart = jest.fn();
render(
<LobbyScreen colyseusClient={mockClient} onGameStart={onGameStart} />
);
fireEvent.click(screen.getByText("Create Game"));
// Wait for the async createGame to resolve and render the code
await waitFor(() => {
expect(screen.getByText("ABCD")).toBeInTheDocument();
});
// Now the onStateChange callback should be registered
const stateChangeCallback = mockClient.onStateChange.mock.calls[0][0];
act(() => {
stateChangeCallback({ players: new Map([["p1", {}], ["p2", {}]]) });
});
expect(onGameStart).toHaveBeenCalledWith("ABCD");
});
});