Files
manage/frontend/src/pages/__tests__/Dashboard.test.tsx
T
Developer 1da67f38c7 feat(services): frontend services runtime and widget rebind
PR 3 of 4 for the runtime service registry change.

- Add service + new-shape widget TypeScript types; widgets carry service_id
  + widget_kind (service-bound) or null (built-in).
- Add services API client + TanStack Query hooks; reconcile the widget API
  client/hooks to the new endpoints (remove sources/types; add builtin kinds).
- Add closed frontend service registry (integrations/registry.ts) mirroring the
  backend, with resolveWidget(widget, services) mapping a widget to its
  component + refresh interval.
- Add ServicePage at /services/:serviceType/:serviceId with config view,
  empty-on-edit secret inputs + 'set' badges, enable toggle, delete, and the
  service's widget-kind list.
- Register /services/:serviceType/:serviceId in App.tsx.
- Reconcile the six widget components to refreshIntervalMs + description props;
  rewrite WidgetConfigDialog around a service -> widget-kind picker.
- Update Dashboard test; add integrations/registry.test.ts.

Verification: frontend lint 0 errors, build success, 70 tests passed; backend
ruff clean, 222 tests passed.
2026-06-22 18:59:41 +00:00

114 lines
3.5 KiB
TypeScript

import { describe, it, expect, vi, beforeEach } from "vitest";
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { Dashboard } from "../Dashboard";
import type { DashboardShortcut } from "../../types";
// Stub the composed widgets so the test exercises Dashboard's own behavior
// (shortcut CRUD) without rendering widgets or their data queries.
vi.mock("../../components/WidgetInstance", () => ({
WidgetInstanceCard: () => <div data-testid="widget-stub" />,
}));
vi.mock("../../components/WidgetConfigDialog", () => ({
WidgetConfigDialog: () => <div data-testid="widget-config-stub" />,
}));
const navigate = vi.fn();
vi.mock("react-router-dom", () => ({
useNavigate: () => navigate,
}));
vi.mock("../../hooks/useSettings", () => ({
useMonitoringSettings: () => ({ data: [] }),
}));
vi.mock("../../hooks/useWidgets", () => ({
useWidgetInstances: () => ({ data: [] }),
}));
const saveShortcutMutate = vi.fn().mockResolvedValue({});
const deleteShortcutMutate = vi.fn();
let shortcuts: DashboardShortcut[] = [];
vi.mock("../../hooks/useDashboard", () => ({
useActivity: () => ({ data: undefined }),
useDashboardShortcuts: () => ({ data: shortcuts }),
useSaveDashboardShortcut: () => ({ mutateAsync: saveShortcutMutate }),
useDeleteDashboardShortcut: () => ({ mutate: deleteShortcutMutate }),
}));
function websiteShortcut(
overrides: Partial<DashboardShortcut> = {},
): DashboardShortcut {
return {
id: "s1",
label: "Wiki",
shortcut_type: "website",
enabled: true,
icon: "📚",
url: "example.com",
task_id: "",
machine_id: "",
user_id: "",
notes: "Team wiki",
created_at: 0,
updated_at: 0,
...overrides,
} as DashboardShortcut;
}
beforeEach(() => {
navigate.mockReset();
saveShortcutMutate.mockClear();
deleteShortcutMutate.mockClear();
shortcuts = [];
});
describe("Dashboard", () => {
it("shows the empty-state alert when there are no shortcuts", () => {
render(<Dashboard />);
expect(screen.getByText(/No shortcuts yet/)).toBeInTheDocument();
expect(
screen.getByRole("button", { name: "Add shortcut" }),
).toBeInTheDocument();
});
it("renders a shortcut card and deletes it via the confirm dialog", async () => {
shortcuts = [websiteShortcut()];
render(<Dashboard />);
expect(screen.getByText("Wiki")).toBeInTheDocument();
// Open the delete confirm.
await userEvent.click(screen.getByRole("button", { name: "Delete" }));
expect(screen.getByText("Delete shortcut?")).toBeInTheDocument();
// Confirm deletion -> delete mutation fires with the shortcut id.
const dialogs = screen.getAllByRole("button", { name: "Delete" });
// The card "Delete" plus the confirm "Delete"; confirm is the last one.
await userEvent.click(dialogs[dialogs.length - 1]);
expect(deleteShortcutMutate).toHaveBeenCalledTimes(1);
expect(deleteShortcutMutate).toHaveBeenCalledWith("s1");
});
it("creates a shortcut via the dialog and saves it", async () => {
render(<Dashboard />);
await userEvent.click(screen.getByRole("button", { name: "Add shortcut" }));
// Edit dialog opens in "New shortcut" mode.
expect(screen.getByText("New shortcut")).toBeInTheDocument();
// Fill the label and save.
await userEvent.type(screen.getByLabelText("Label"), "Grafana");
await userEvent.click(
screen.getByRole("button", { name: "Save shortcut" }),
);
expect(saveShortcutMutate).toHaveBeenCalledTimes(1);
const saved = saveShortcutMutate.mock.calls[0][0];
expect(saved.label).toBe("Grafana");
expect(saved.shortcut_type).toBe("website");
});
});