Files
Termix/src/ui/tests/sidebar/AppRail.test.tsx
T

207 lines
6.4 KiB
TypeScript
Raw Normal View History

2026-10-01 16:20:16 -05:00
import { afterEach, describe, expect, it, vi } from "vitest";
2026-10-10 14:58:27 -05:00
import { cleanup, fireEvent, render, screen } from "@testing-library/react";
2026-10-01 16:20:16 -05:00
import userEvent from "@testing-library/user-event";
import { Bell, Boxes } from "lucide-react";
import { AppRail } from "../../sidebar/AppRail";
import { RailBadge } from "../../sidebar/RailBadge";
import {
registerRailItem,
resetRegisteredRailItems,
} from "../../sidebar/rail-items";
vi.mock("react-i18next", () => ({
useTranslation: () => ({ t: (key: string) => key }),
}));
2026-10-10 14:58:27 -05:00
vi.mock("@/main-axios", () => ({
getUserPreferences: vi.fn(async () => ({ storageMode: "local" })),
saveUserPreferences: vi.fn(async () => undefined),
}));
2026-10-01 16:20:16 -05:00
vi.mock("@/hooks/use-permissions", () => ({
usePermissions: () => ({ has: () => true, loaded: true }),
}));
afterEach(() => {
resetRegisteredRailItems();
localStorage.clear();
2026-10-10 14:58:27 -05:00
delete (window as { IS_ELECTRON?: boolean }).IS_ELECTRON;
2026-10-01 16:20:16 -05:00
});
function renderRail(onRailClick = vi.fn()) {
render(
<AppRail
railView="hosts"
sidebarOpen={false}
username="alice"
isAdmin={false}
onRailClick={onRailClick}
2026-10-10 14:58:27 -05:00
onOpenSettings={vi.fn()}
onOpenNavigationSettings={vi.fn()}
2026-10-01 16:20:16 -05:00
onLogout={vi.fn()}
/>,
);
return onRailClick;
}
describe("AppRail", () => {
2026-10-10 14:58:27 -05:00
it("shows logout in the browser but not on the desktop", () => {
renderRail();
expect(screen.getByLabelText("common.logout")).toBeTruthy();
cleanup();
(window as { IS_ELECTRON?: boolean }).IS_ELECTRON = true;
renderRail();
expect(screen.queryByLabelText("common.logout")).toBeNull();
});
2026-10-18 00:34:50 -05:00
it("expands over the content on hover without taking more room", () => {
renderRail();
const rail = screen
.getByLabelText("common.logout")
.closest(".absolute") as HTMLElement;
const slot = rail.parentElement as HTMLElement;
const collapsed = slot.style.width;
fireEvent.mouseEnter(rail);
expect(rail.style.width).not.toBe(collapsed);
expect(slot.style.width).toBe(collapsed);
});
it("pushes the content on hover when the user asks for it", () => {
localStorage.setItem("appRailHoverPushesContent", "true");
renderRail();
const rail = screen
.getByLabelText("common.logout")
.closest(".absolute") as HTMLElement;
const slot = rail.parentElement as HTMLElement;
fireEvent.mouseEnter(rail);
expect(slot.style.width).toBe(rail.style.width);
});
2026-10-01 16:20:16 -05:00
it("puts a footer item above the profile with its badge", async () => {
registerRailItem({
id: "inbox",
icon: Bell,
labelKey: "test.inbox",
placement: "footer",
useBadge: () => 3,
pluginId: "demo",
});
registerRailItem({
id: "boxes",
icon: Boxes,
labelKey: "test.boxes",
pluginId: "demo",
});
const onRailClick = renderRail();
const inbox = screen.getByTitle("test.inbox");
2026-10-10 14:58:27 -05:00
const profile = screen.getAllByTitle("nav.openSettings")[0];
2026-10-01 16:20:16 -05:00
const boxes = screen.getByTitle("test.boxes");
// Footer items render after the main list and before the profile.
expect(
boxes.compareDocumentPosition(inbox) & Node.DOCUMENT_POSITION_FOLLOWING,
).toBeTruthy();
expect(
inbox.compareDocumentPosition(profile) & Node.DOCUMENT_POSITION_FOLLOWING,
).toBeTruthy();
expect(inbox.textContent).toContain("3");
await userEvent.click(inbox);
expect(onRailClick).toHaveBeenCalledWith("inbox");
});
it("hides a footer item the user hid from Navigation", () => {
localStorage.setItem("hiddenRailTabs", JSON.stringify(["inbox"]));
registerRailItem({
id: "inbox",
icon: Bell,
labelKey: "test.inbox",
placement: "footer",
pluginId: "demo",
});
renderRail();
expect(screen.queryByTitle("test.inbox")).toBeNull();
});
2026-10-10 14:58:27 -05:00
it("groups items into bands with a labelled divider", () => {
registerRailItem({
id: "fleets",
icon: Boxes,
labelKey: "test.fleets",
group: "objects",
pluginId: "demo",
});
renderRail();
expect(screen.getByText("nav.group.tools")).toBeTruthy();
const fleets = screen.getByTitle("test.fleets");
const connections = screen.getByTitle("nav.connections");
expect(
fleets.compareDocumentPosition(connections) &
Node.DOCUMENT_POSITION_FOLLOWING,
).toBeTruthy();
});
it("lists hidden items and brings one back", async () => {
localStorage.setItem("hiddenRailTabs", JSON.stringify(["connections"]));
renderRail();
expect(screen.queryByTitle("nav.connections")).toBeNull();
await userEvent.click(screen.getByTitle("nav.hiddenCount"));
await userEvent.click(screen.getByTitle("nav.showInRail"));
expect(JSON.parse(localStorage.getItem("hiddenRailTabs")!)).toEqual([]);
expect(await screen.findByTitle("nav.connections")).toBeTruthy();
});
it("opens plain settings from the account button", async () => {
const onOpenSettings = vi.fn();
const onOpenNavigationSettings = vi.fn();
render(
<AppRail
railView="hosts"
sidebarOpen={false}
username="alice"
isAdmin
onRailClick={vi.fn()}
onOpenSettings={onOpenSettings}
onOpenNavigationSettings={onOpenNavigationSettings}
onLogout={vi.fn()}
/>,
);
await userEvent.click(screen.getByLabelText("nav.openSettings"));
expect(onOpenSettings).toHaveBeenCalled();
expect(onOpenNavigationSettings).not.toHaveBeenCalled();
});
it("opens the navigation settings from the rail menu", async () => {
const onOpenSettings = vi.fn();
const onOpenNavigationSettings = vi.fn();
render(
<AppRail
railView="hosts"
sidebarOpen={false}
username="alice"
isAdmin
onRailClick={vi.fn()}
onOpenSettings={onOpenSettings}
onOpenNavigationSettings={onOpenNavigationSettings}
onLogout={vi.fn()}
/>,
);
fireEvent.contextMenu(screen.getByTitle("nav.connections"));
await userEvent.click(screen.getByText("nav.openSettings"));
expect(onOpenNavigationSettings).toHaveBeenCalled();
expect(onOpenSettings).not.toHaveBeenCalled();
});
2026-10-01 16:20:16 -05:00
});
describe("RailBadge", () => {
it("shows nothing for zero", () => {
const { container } = render(<RailBadge useBadge={() => 0} />);
expect(container.textContent).toBe("");
});
it("caps a large count", () => {
render(<RailBadge useBadge={() => 150} />);
expect(screen.getByText("99+")).toBeTruthy();
});
});