- 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
162 lines
5.0 KiB
JavaScript
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");
|
|
});
|
|
});
|