Files
fusion/packages/dashboard/app/components/__tests__/SystemStatsModal.test.tsx
2026-04-27 08:45:49 -07:00

272 lines
9.3 KiB
TypeScript

import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
import { act, fireEvent, render, screen, waitFor } from "@testing-library/react";
import { SystemStatsModal } from "../SystemStatsModal";
vi.mock("lucide-react", () => ({
Monitor: (props: { className?: string }) => <span data-testid="icon-monitor" {...props} />,
RefreshCw: (props: { className?: string }) => <span data-testid="icon-refresh" {...props} />,
ShieldAlert: (props: { className?: string }) => <span data-testid="icon-shield-alert" {...props} />,
Skull: (props: { className?: string }) => <span data-testid="icon-skull" {...props} />,
X: (props: { className?: string }) => <span data-testid="icon-x" {...props} />,
}));
const mockFetchSystemStats = vi.fn();
const mockFetchGlobalSettings = vi.fn();
const mockKillVitestProcesses = vi.fn();
const mockUpdateGlobalSettings = vi.fn();
vi.mock("../../api", () => ({
fetchSystemStats: (...args: unknown[]) => mockFetchSystemStats(...args),
fetchGlobalSettings: (...args: unknown[]) => mockFetchGlobalSettings(...args),
killVitestProcesses: (...args: unknown[]) => mockKillVitestProcesses(...args),
updateGlobalSettings: (...args: unknown[]) => mockUpdateGlobalSettings(...args),
}));
const sampleStats = {
systemStats: {
rss: 5 * 1024 * 1024 * 1024,
heapUsed: 900 * 1024 * 1024,
heapTotal: 1200 * 1024 * 1024,
heapLimit: 1000 * 1024 * 1024,
external: 50 * 1024 * 1024,
arrayBuffers: 20 * 1024 * 1024,
cpuPercent: null,
loadAvg: [1.2, 0.8, 0.5] as [number, number, number],
cpuCount: 8,
systemTotalMem: 10 * 1024 * 1024 * 1024,
systemFreeMem: 1024 * 1024 * 1024,
pid: 12345,
nodeVersion: "v22.0.0",
platform: "darwin/arm64",
},
taskStats: {
total: 6,
byColumn: {
triage: 1,
todo: 2,
"in-progress": 1,
"in-review": 1,
done: 1,
archived: 0,
},
active: 2,
agents: {
idle: 1,
active: 2,
running: 0,
error: 1,
},
},
vitestProcessCount: 2,
vitestLastAutoKillAt: "2026-04-27T12:00:00.000Z",
};
describe("SystemStatsModal", () => {
beforeEach(() => {
vi.clearAllMocks();
mockFetchSystemStats.mockResolvedValue(sampleStats);
mockFetchGlobalSettings.mockResolvedValue({
vitestAutoKillEnabled: true,
vitestKillThresholdPct: 90,
});
mockKillVitestProcesses.mockResolvedValue({ killed: 2, pids: [111, 222] });
mockUpdateGlobalSettings.mockResolvedValue({});
});
afterEach(() => {
vi.useRealTimers();
});
it("shows loading state while initial stats are fetched", async () => {
mockFetchSystemStats.mockReturnValue(new Promise(() => undefined));
render(<SystemStatsModal isOpen={true} onClose={vi.fn()} />);
expect(await screen.findByText("Loading system stats…")).toBeDefined();
});
it("renders fetched metrics across all sections", async () => {
render(<SystemStatsModal isOpen={true} onClose={vi.fn()} projectId="proj-1" />);
await waitFor(() => {
expect(mockFetchSystemStats).toHaveBeenCalledWith("proj-1");
expect(mockFetchGlobalSettings).toHaveBeenCalledTimes(1);
});
expect(screen.getByText("System Stats")).toBeDefined();
expect(await screen.findByText("Process")).toBeDefined();
expect(screen.getByText("CPU & Load")).toBeDefined();
expect(screen.getByText("System")).toBeDefined();
expect(screen.getByText("Tasks")).toBeDefined();
expect(screen.getByText("Agents")).toBeDefined();
expect(screen.getByText("Vitest Controls")).toBeDefined();
expect(screen.getByText("5.00 GB")).toBeDefined();
expect(screen.getByText("900 MB")).toBeDefined();
expect(screen.getByText("9.00 GB")).toBeDefined();
expect(screen.getByText("90.0% of 10.00 GB")).toBeDefined();
expect(screen.getByText("1.20 0.80 0.50")).toBeDefined();
expect(screen.getByText("Vitest Processes")).toBeDefined();
expect(screen.getByText(/Last auto-kill:/)).toBeDefined();
const criticalValues = document.querySelectorAll(".system-stats-modal__value--critical");
expect(criticalValues.length).toBeGreaterThan(0);
});
it("shows refresh button icon when the modal is open", async () => {
render(<SystemStatsModal isOpen={true} onClose={vi.fn()} />);
const refreshButton = await screen.findByRole("button", { name: "Refresh system stats" });
const refreshIcon = screen.getByTestId("icon-refresh");
expect(refreshButton).toBeDefined();
expect(refreshIcon).toBeDefined();
expect(refreshButton.contains(refreshIcon)).toBe(true);
});
it("shows auto-refresh indicator when stats are loaded", async () => {
render(<SystemStatsModal isOpen={true} onClose={vi.fn()} />);
expect(await screen.findByText("Auto-refresh · 5s")).toBeDefined();
expect(screen.getByText(/Updated|Waiting for first update/)).toBeDefined();
});
it("applies spinning class to refresh icon during background refresh", async () => {
vi.useFakeTimers();
let callCount = 0;
let resolveBackgroundRefresh: (() => void) | undefined;
const backgroundRefreshPromise = new Promise<typeof sampleStats>((resolve) => {
resolveBackgroundRefresh = () => resolve(sampleStats);
});
mockFetchSystemStats.mockImplementation(() => {
callCount += 1;
if (callCount === 1) {
return Promise.resolve(sampleStats);
}
return backgroundRefreshPromise;
});
render(<SystemStatsModal isOpen={true} onClose={vi.fn()} />);
await act(async () => {
await Promise.resolve();
});
expect(screen.getByText("Auto-refresh · 5s")).toBeDefined();
act(() => {
vi.advanceTimersByTime(5_000);
});
await act(async () => {
await Promise.resolve();
});
expect(screen.getByTestId("icon-refresh").className).toContain("system-stats-modal__refresh--spinning");
resolveBackgroundRefresh?.();
await act(async () => {
await Promise.resolve();
});
});
it("shows error state when initial fetch fails", async () => {
mockFetchSystemStats.mockRejectedValue(new Error("stats unavailable"));
render(<SystemStatsModal isOpen={true} onClose={vi.fn()} />);
expect(await screen.findByRole("alert")).toHaveTextContent("stats unavailable");
});
it("requires a confirmation click before killing vitest processes", async () => {
render(<SystemStatsModal isOpen={true} onClose={vi.fn()} projectId="proj-1" />);
const killButton = await screen.findByRole("button", { name: /Kill Vitest Processes/i });
fireEvent.click(killButton);
expect(mockKillVitestProcesses).not.toHaveBeenCalled();
expect(screen.getByRole("button", { name: /Confirm Kill\?/i })).toBeDefined();
fireEvent.click(screen.getByRole("button", { name: /Confirm Kill\?/i }));
await waitFor(() => {
expect(mockKillVitestProcesses).toHaveBeenCalledWith("proj-1");
expect(screen.getByText("Killed 2 processes")).toBeDefined();
});
});
it("persists auto-kill toggle changes", async () => {
render(<SystemStatsModal isOpen={true} onClose={vi.fn()} />);
const toggle = (await screen.findByLabelText("Auto-kill vitest on memory pressure")) as HTMLInputElement;
expect(toggle.checked).toBe(true);
fireEvent.click(toggle);
await waitFor(() => {
expect(mockUpdateGlobalSettings).toHaveBeenCalledWith({ vitestAutoKillEnabled: false });
});
});
it("clamps threshold input to allowed range", async () => {
render(<SystemStatsModal isOpen={true} onClose={vi.fn()} />);
const thresholdInput = (await screen.findByLabelText("Kill threshold (%)")) as HTMLInputElement;
fireEvent.change(thresholdInput, { target: { value: "20" } });
await waitFor(() => {
expect(mockUpdateGlobalSettings).toHaveBeenCalledWith({ vitestKillThresholdPct: 50 });
});
fireEvent.change(thresholdInput, { target: { value: "120" } });
await waitFor(() => {
expect(mockUpdateGlobalSettings).toHaveBeenCalledWith({ vitestKillThresholdPct: 99 });
});
});
it("persists threshold changes from the slider control", async () => {
render(<SystemStatsModal isOpen={true} onClose={vi.fn()} />);
const thresholdSlider = (await screen.findByLabelText("Kill threshold slider (%)")) as HTMLInputElement;
fireEvent.change(thresholdSlider, { target: { value: "95" } });
await waitFor(() => {
expect(mockUpdateGlobalSettings).toHaveBeenCalledWith({ vitestKillThresholdPct: 95 });
});
});
it("shows fallback text when last auto-kill timestamp is unavailable", async () => {
mockFetchSystemStats.mockResolvedValue({
...sampleStats,
vitestLastAutoKillAt: null,
});
render(<SystemStatsModal isOpen={true} onClose={vi.fn()} />);
expect(await screen.findByText("Last auto-kill: Not yet")).toBeDefined();
});
it("refreshes every 5 seconds while open and stops when closed", async () => {
vi.useFakeTimers();
const { rerender } = render(<SystemStatsModal isOpen={true} onClose={vi.fn()} />);
await act(async () => {
await Promise.resolve();
});
expect(mockFetchSystemStats).toHaveBeenCalledTimes(1);
await act(async () => {
await vi.advanceTimersByTimeAsync(5_000);
});
expect(mockFetchSystemStats).toHaveBeenCalledTimes(2);
rerender(<SystemStatsModal isOpen={false} onClose={vi.fn()} />);
await act(async () => {
await vi.advanceTimersByTimeAsync(10_000);
});
expect(mockFetchSystemStats).toHaveBeenCalledTimes(2);
});
});