feat: implement frontend with TDD (Part 8)
- Scaffold Vite + React 19 + TypeScript strict project - Neumorphic dark design system (Tailwind v3, amber/orange LED accents) - Zustand stores for auth (persist) and UI state (notifications, sidebar) - TanStack Query v5 hooks for server CRUD operations - WebSocket hook with reconnection backoff and query invalidation - Components: StatusLed, Sidebar, ServerCard, LoginPage, DashboardPage - Protected routing with auth guard - Axios client with JWT interceptor and 401 redirect - 68 tests across 11 test files (89% statement coverage, 90% function coverage) - TDD workflow: RED validated, GREEN achieved, coverage verified
This commit is contained in:
116
frontend/src/__tests__/ServerCard.test.tsx
Normal file
116
frontend/src/__tests__/ServerCard.test.tsx
Normal file
@@ -0,0 +1,116 @@
|
||||
import { describe, it, expect, vi, beforeEach } from "vitest";
|
||||
import { render, screen } from "@testing-library/react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||
|
||||
import { ServerCard } from "@/components/servers/ServerCard";
|
||||
import type { Server } from "@/hooks/useServers";
|
||||
import {
|
||||
useStartServer,
|
||||
useStopServer,
|
||||
useRestartServer,
|
||||
} from "@/hooks/useServers";
|
||||
|
||||
vi.mock("@/hooks/useServers", () => ({
|
||||
useStartServer: vi.fn(),
|
||||
useStopServer: vi.fn(),
|
||||
useRestartServer: vi.fn(),
|
||||
}));
|
||||
|
||||
const mockMutation = (resolve?: boolean) => ({
|
||||
mutateAsync: vi.fn(() => (resolve === false ? Promise.reject(new Error("fail")) : Promise.resolve())),
|
||||
isPending: false,
|
||||
});
|
||||
|
||||
function renderCard(server: Partial<Server> = {}) {
|
||||
const fullServer: Server = {
|
||||
id: 1,
|
||||
name: "Test Arma3",
|
||||
game_type: "arma3",
|
||||
status: "running",
|
||||
port: 2302,
|
||||
max_players: 64,
|
||||
current_players: 32,
|
||||
restart_count: 3,
|
||||
auto_restart: true,
|
||||
created_at: "2026-01-01T00:00:00Z",
|
||||
...server,
|
||||
};
|
||||
|
||||
const queryClient = new QueryClient({
|
||||
defaultOptions: { queries: { retry: false } },
|
||||
});
|
||||
|
||||
return {
|
||||
user: userEvent.setup(),
|
||||
...render(
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<ServerCard server={fullServer} />
|
||||
</QueryClientProvider>,
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
describe("ServerCard", () => {
|
||||
beforeEach(() => {
|
||||
vi.mocked(useStartServer).mockReturnValue(mockMutation() as unknown as ReturnType<typeof useStartServer>);
|
||||
vi.mocked(useStopServer).mockReturnValue(mockMutation() as unknown as ReturnType<typeof useStopServer>);
|
||||
vi.mocked(useRestartServer).mockReturnValue(mockMutation() as unknown as ReturnType<typeof useRestartServer>);
|
||||
});
|
||||
|
||||
it("should render server name and game type", () => {
|
||||
renderCard();
|
||||
expect(screen.getByText("Test Arma3")).toBeInTheDocument();
|
||||
expect(screen.getByText("arma3")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should display player count", () => {
|
||||
renderCard();
|
||||
expect(screen.getByText("32/64")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should display port number", () => {
|
||||
renderCard({ port: 2302 });
|
||||
expect(screen.getByText("2302")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should display restart count", () => {
|
||||
renderCard({ restart_count: 3 });
|
||||
expect(screen.getByText("3")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should show Stop button when server is running", () => {
|
||||
renderCard({ status: "running" });
|
||||
expect(screen.getByLabelText("Stop Test Arma3")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should show Start button when server is stopped", () => {
|
||||
renderCard({ status: "stopped" });
|
||||
expect(screen.getByLabelText("Start Test Arma3")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should not show Start button when server is running", () => {
|
||||
renderCard({ status: "running" });
|
||||
expect(screen.queryByLabelText(/Start Test Arma3/)).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should show Restart button when server is running", () => {
|
||||
renderCard({ status: "running" });
|
||||
expect(screen.getByLabelText("Restart Test Arma3")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should disable Stop button when server is starting", () => {
|
||||
renderCard({ status: "starting" });
|
||||
const stopBtn = screen.getByLabelText("Stop Test Arma3");
|
||||
expect(stopBtn).toBeDisabled();
|
||||
});
|
||||
|
||||
it("should call startServer on Start click", async () => {
|
||||
const startMutation = mockMutation();
|
||||
vi.mocked(useStartServer).mockReturnValue(startMutation as unknown as ReturnType<typeof useStartServer>);
|
||||
|
||||
const { user } = renderCard({ status: "stopped" });
|
||||
await user.click(screen.getByLabelText("Start Test Arma3"));
|
||||
expect(startMutation.mutateAsync).toHaveBeenCalledWith(1);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user