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:
Tran G. (Revernomad) Khoa
2026-04-16 23:53:25 +07:00
parent b17d199301
commit 88424675b5
43 changed files with 8144 additions and 0 deletions

View 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);
});
});