Files
silo-server/web/src/components/AdminSidebar.test.tsx
T
QuickandClaude Fable 5 89f005dfa8 feat(web): regroup admin navigation and settings into intuitive sections
Reorganize the admin sidebar into Overview / Content / Automation /
Users / System so media pipeline tools no longer hide under Users and
Settings is not buried in a seven-item Server grab-bag. Group the flat
13-tab admin settings page into Server / Media / Connections / Data
sections, and split the Integrations tab into dedicated Subtitles
(search providers + AI translation) and Watch Providers (Trakt/Simkl
OAuth) pages, leaving Integrations for one-off keys like MDBList. All
routes and ?tab= ids are unchanged so deep links keep working.

Extract the shared grouped-rail markup used by the admin sidebar, admin
settings nav, and user settings nav into a SideNav component, and
remove the orphaned admin-settings PluginsSettings page (superseded by
/admin/plugins).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-09 14:23:10 -04:00

143 lines
3.8 KiB
TypeScript

import { renderToStaticMarkup } from "react-dom/server";
import { MemoryRouter } from "react-router";
import { describe, expect, it, vi } from "vitest";
import AdminSidebar from "./AdminSidebar";
import type { BuildInfo } from "@/hooks/queries/admin/system";
interface MockBuildInfoResult {
data?: BuildInfo;
isPending: boolean;
isError: boolean;
}
const mockUseServerBranding = vi.fn(() => ({
serverName: "Silo",
loginSubtitle: "Sign in with an existing account.",
}));
const defaultBuildInfo: BuildInfo = {
display: "b4c5aae1+dirty",
revision: "b4c5aae18aa653725ac697b29a05eac797576008",
dirty: true,
vcs_time: "2026-04-05T22:24:40Z",
available: true,
};
const mockUseBuildInfo = vi.fn<() => MockBuildInfoResult>(() => ({
data: defaultBuildInfo,
isPending: false,
isError: false,
}));
const mockUseAdminSessions = vi.fn(() => ({ data: [] }));
const mockUseAdminPluginInstallations = vi.fn(() => ({ data: [] }));
vi.mock("@/hooks/useServerBranding", () => ({
useServerBranding: () => mockUseServerBranding(),
}));
vi.mock("@/hooks/queries/admin/system", () => ({
useBuildInfo: () => mockUseBuildInfo(),
}));
vi.mock("@/hooks/queries/admin/stats", () => ({
useAdminSessions: () => mockUseAdminSessions(),
}));
vi.mock("@/hooks/queries/admin/plugins", () => ({
useAdminPluginInstallations: () => mockUseAdminPluginInstallations(),
}));
function renderSidebar() {
return renderToStaticMarkup(
<MemoryRouter initialEntries={["/admin"]}>
<AdminSidebar />
</MemoryRouter>,
);
}
describe("AdminSidebar", () => {
it("renders the grouped navigation sections", () => {
const markup = renderSidebar();
for (const section of ["Overview", "Content", "Automation", "Users", "System"]) {
expect(markup).toContain(`>${section}<`);
}
});
it("includes a Sections link in the content navigation", () => {
const markup = renderSidebar();
expect(markup).toContain('href="/admin/sections"');
expect(markup).toContain(">Sections<");
});
it("includes a Maintenance link in the system navigation", () => {
const markup = renderSidebar();
expect(markup).toContain('href="/admin/maintenance"');
expect(markup).toContain(">Maintenance<");
});
it("includes a Recommendations link in the automation navigation", () => {
const markup = renderSidebar();
expect(markup).toContain('href="/admin/recommendations"');
expect(markup).toContain(">Recommendations<");
});
it("includes a Markers link in the automation navigation", () => {
const markup = renderSidebar();
expect(markup).toContain('href="/admin/marker-history"');
expect(markup).toContain(">Markers<");
});
it("renders the build identifier in the footer", () => {
const markup = renderSidebar();
expect(markup).toContain(">Build<");
expect(markup).toContain(">b4c5aae1+dirty<");
});
it("renders dev build when build metadata is missing", () => {
mockUseBuildInfo.mockReturnValueOnce({
data: {
...defaultBuildInfo,
display: "unavailable",
revision: "",
dirty: false,
vcs_time: "",
available: false,
},
isPending: false,
isError: false,
});
const markup = renderSidebar();
expect(markup).toContain(">dev build<");
});
it("renders load failed when the build info query errors", () => {
mockUseBuildInfo.mockReturnValueOnce({
data: undefined,
isPending: false,
isError: true,
});
const markup = renderSidebar();
expect(markup).toContain(">load failed<");
});
it("renders loading while the build info query is pending", () => {
mockUseBuildInfo.mockReturnValueOnce({
data: undefined,
isPending: true,
isError: false,
});
const markup = renderSidebar();
expect(markup).toContain(">loading...<");
});
});