f78c7f9a2e
Internal SmartGift build of a Claude Code monitoring dashboard. Lanes: a durable unit of parallel agent work, one per working directory, tracked across session restarts. Managed lanes are git worktrees the dashboard provisions and can reset or remove behind a three-check destroy guard and a counted preflight; adopted lanes are directories you already own and are never destroyable. Pipelines: a lane moves through pipeline stages. A stage the agent declares with evidence renders green; a stage inferred from the tool-event stream renders dashed amber and never counts as done. Detection is forward-only within a 30-minute window, and never writes the declared stage. Workspace: one page at /run with a lane grid, the selected lane's pipeline, and a full Claude console behind a disclosure.
101 lines
3.8 KiB
TypeScript
101 lines
3.8 KiB
TypeScript
/**
|
|
* @file Sidebar.test.tsx
|
|
* @description Unit tests for the Sidebar component, which is responsible for rendering the application's sidebar navigation. The tests cover rendering of the brand name, subtitle, navigation links, WebSocket connection status, and version number. The tests use React Testing Library and Vitest for assertions and mocking.
|
|
* @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
|
|
*/
|
|
|
|
import { describe, it, expect } from "vitest";
|
|
import { render, screen, waitFor } from "@testing-library/react";
|
|
import userEvent from "@testing-library/user-event";
|
|
import { MemoryRouter } from "react-router-dom";
|
|
import { Sidebar } from "../Sidebar";
|
|
|
|
function renderSidebar(wsConnected: boolean, collapsed = false) {
|
|
return render(
|
|
<MemoryRouter>
|
|
<Sidebar wsConnected={wsConnected} collapsed={collapsed} onToggle={() => {}} />
|
|
</MemoryRouter>
|
|
);
|
|
}
|
|
|
|
describe("Sidebar", () => {
|
|
it("should render the brand name", () => {
|
|
renderSidebar(true);
|
|
expect(screen.getByText("Agent Dashboard")).toBeInTheDocument();
|
|
});
|
|
|
|
it("should render the subtitle", () => {
|
|
renderSidebar(true);
|
|
expect(screen.getByText("Claude Code Monitor")).toBeInTheDocument();
|
|
});
|
|
|
|
it("should render all navigation links", () => {
|
|
renderSidebar(true);
|
|
expect(screen.getByText("Dashboard")).toBeInTheDocument();
|
|
expect(screen.getByText("Kanban Board")).toBeInTheDocument();
|
|
expect(screen.getByText("Sessions")).toBeInTheDocument();
|
|
expect(screen.getByText("Activity Feed")).toBeInTheDocument();
|
|
});
|
|
|
|
it('should show "Live" when WebSocket is connected', () => {
|
|
renderSidebar(true);
|
|
expect(screen.getByText("Live")).toBeInTheDocument();
|
|
});
|
|
|
|
it('should show "Disconnected" when WebSocket is not connected', () => {
|
|
renderSidebar(false);
|
|
expect(screen.getByText("Disconnected")).toBeInTheDocument();
|
|
});
|
|
|
|
it("should show version number", () => {
|
|
// `__APP_VERSION__` is injected by Vite from the repo-root package.json
|
|
// (see vite.config.ts) and replaced at transform time in tests too, so this
|
|
// stays correct as the project version changes.
|
|
renderSidebar(true);
|
|
expect(screen.getByText(`v${__APP_VERSION__}`)).toBeInTheDocument();
|
|
});
|
|
|
|
it("should have correct navigation hrefs", () => {
|
|
renderSidebar(true);
|
|
const links = screen.getAllByRole("link");
|
|
const hrefs = links.map((link) => link.getAttribute("href"));
|
|
expect(hrefs).toContain("/");
|
|
expect(hrefs).toContain("/kanban");
|
|
expect(hrefs).toContain("/sessions");
|
|
expect(hrefs).toContain("/activity");
|
|
});
|
|
|
|
it("should render both language options in expanded mode", () => {
|
|
renderSidebar(true);
|
|
expect(screen.getByRole("button", { name: "English" })).toBeInTheDocument();
|
|
expect(screen.getByRole("button", { name: "Vietnamese" })).toBeInTheDocument();
|
|
// Chinese and Korean were dropped; offering them would render raw keys.
|
|
expect(screen.queryByRole("button", { name: "Chinese" })).toBeNull();
|
|
expect(screen.queryByRole("button", { name: "Korean" })).toBeNull();
|
|
});
|
|
|
|
it("should switch to Vietnamese when Vietnamese option is clicked", async () => {
|
|
const user = userEvent.setup();
|
|
renderSidebar(true);
|
|
|
|
await user.click(screen.getByRole("button", { name: "Vietnamese" }));
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByText("Tổng quan")).toBeInTheDocument();
|
|
expect(screen.getByText("Bảng Kanban")).toBeInTheDocument();
|
|
});
|
|
});
|
|
|
|
it("should cycle language in collapsed mode", async () => {
|
|
const user = userEvent.setup();
|
|
renderSidebar(true, true);
|
|
|
|
expect(screen.getByText("EN")).toBeInTheDocument();
|
|
await user.click(screen.getByRole("button", { name: "Switch to Vietnamese" }));
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByText("VI")).toBeInTheDocument();
|
|
});
|
|
});
|
|
});
|