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>
143 lines
3.8 KiB
TypeScript
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...<");
|
|
});
|
|
});
|