feat: Claude Code Monitor — lanes, pipelines and a merged workspace
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.
This commit is contained in:
@@ -0,0 +1,74 @@
|
||||
/**
|
||||
* @file StatCard.test.tsx
|
||||
* @description Unit tests for the StatCard component, which is a reusable React component that displays a statistic with a label, value, icon, and optional trend information. The tests cover rendering of the label, value (both numeric and string), trend information, and the icon. The tests also verify that custom accent colors are applied correctly. 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 } from "@testing-library/react";
|
||||
import { StatCard } from "../StatCard";
|
||||
import { Activity } from "lucide-react";
|
||||
|
||||
describe("StatCard", () => {
|
||||
it("should render label", () => {
|
||||
render(<StatCard label="Total Sessions" value={42} icon={Activity} />);
|
||||
expect(screen.getByText("Total Sessions")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should render numeric value", () => {
|
||||
render(<StatCard label="Events" value={156} icon={Activity} />);
|
||||
expect(screen.getByText("156")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should render string value", () => {
|
||||
render(<StatCard label="Status" value="-" icon={Activity} />);
|
||||
expect(screen.getByText("-")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should render trend when provided", () => {
|
||||
render(<StatCard label="Sessions" value={10} icon={Activity} trend="3 active" />);
|
||||
expect(screen.getByText("3 active")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should not render trend when not provided", () => {
|
||||
render(<StatCard label="Sessions" value={10} icon={Activity} />);
|
||||
expect(screen.queryByText("active")).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should render the icon", () => {
|
||||
const { container } = render(<StatCard label="Test" value={0} icon={Activity} />);
|
||||
// Lucide renders as SVG
|
||||
const svg = container.querySelector("svg");
|
||||
expect(svg).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should apply custom accent color", () => {
|
||||
const { container } = render(
|
||||
<StatCard label="Test" value={0} icon={Activity} accentColor="text-emerald-400" />
|
||||
);
|
||||
const svg = container.querySelector("svg");
|
||||
expect(svg?.className?.baseVal ?? svg?.getAttribute("class")).toContain("text-emerald-400");
|
||||
});
|
||||
|
||||
it("should apply default accent color when not specified", () => {
|
||||
const { container } = render(<StatCard label="Test" value={0} icon={Activity} />);
|
||||
const svg = container.querySelector("svg");
|
||||
expect(svg?.className?.baseVal ?? svg?.getAttribute("class")).toContain("text-accent");
|
||||
});
|
||||
|
||||
it("should render a skeleton placeholder when loading and hide the real value", () => {
|
||||
const { container } = render(<StatCard label="Total" value="" icon={Activity} loading />);
|
||||
// value text should NOT appear so users never see a flash of "-" or 0
|
||||
expect(screen.queryByText("-")).not.toBeInTheDocument();
|
||||
expect(screen.queryByText("0")).not.toBeInTheDocument();
|
||||
// skeleton primitive renders an aria-busy node
|
||||
expect(container.querySelector('[aria-busy="true"]')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should swap from skeleton to value when loading flips false", () => {
|
||||
const { rerender } = render(<StatCard label="Total" value="" icon={Activity} loading />);
|
||||
expect(screen.queryByText("42")).not.toBeInTheDocument();
|
||||
rerender(<StatCard label="Total" value={42} icon={Activity} loading={false} />);
|
||||
expect(screen.getByText("42")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user