mirror of
https://github.com/Buriburizaem0n/nezha-dash-v1.git
synced 2026-09-19 09:40:14 +00:00
- Created tests for formatBytes function to ensure correct formatting of byte values. - Implemented tests for InjectContext to validate resource injection and cleanup. - Added tests for logo-class helpers to verify platform alias mappings. - Developed tests for nezha-api functions to check API response handling. - Introduced tests for static geographic data to validate country coordinates and GeoJSON structure. - Created comprehensive tests for utility functions including date formatting and public note parsing. - Added tests for Server page to ensure correct rendering and functionality. - Implemented tests for simple pages including error handling and navigation. - Set up testing environment with Vitest and configured coverage reporting.
244 lines
6.9 KiB
TypeScript
244 lines
6.9 KiB
TypeScript
import { QueryClientProvider } from "@tanstack/react-query";
|
|
import { render, screen, waitFor } from "@testing-library/react";
|
|
import userEvent from "@testing-library/user-event";
|
|
import type { ReactElement } from "react";
|
|
import { MemoryRouter, useLocation } from "react-router-dom";
|
|
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
import Header, { RefreshToast } from "@/components/Header";
|
|
import { ThemeProvider } from "@/components/ThemeProvider";
|
|
import { CommandProvider } from "@/context/command-provider";
|
|
import { createSettingResponse } from "@/test/fixtures";
|
|
import { createTestQueryClient } from "@/test/utils";
|
|
|
|
const headerMocks = vi.hoisted(() => ({
|
|
backgroundImage: undefined as string | undefined,
|
|
connected: true,
|
|
fetchLoginUser: vi.fn(),
|
|
fetchSetting: vi.fn(),
|
|
lastMessage: null as MessageEvent<string> | null,
|
|
needReconnect: false,
|
|
setNeedReconnect: vi.fn(),
|
|
updateBackground: vi.fn(),
|
|
}));
|
|
|
|
vi.mock("@numeric-text/react", () => ({
|
|
default: ({ value }: { value: string | number }) => (
|
|
<span data-testid="numeric-text">{value}</span>
|
|
),
|
|
}));
|
|
|
|
vi.mock("@/hooks/use-background", () => ({
|
|
useBackground: () => ({
|
|
backgroundImage: headerMocks.backgroundImage,
|
|
updateBackground: headerMocks.updateBackground,
|
|
}),
|
|
}));
|
|
|
|
vi.mock("@/hooks/use-websocket-context", () => ({
|
|
useWebSocketContext: () => ({
|
|
connected: headerMocks.connected,
|
|
lastMessage: headerMocks.lastMessage,
|
|
needReconnect: headerMocks.needReconnect,
|
|
setNeedReconnect: headerMocks.setNeedReconnect,
|
|
}),
|
|
}));
|
|
|
|
vi.mock("@/lib/nezha-api", () => ({
|
|
fetchLoginUser: headerMocks.fetchLoginUser,
|
|
fetchSetting: headerMocks.fetchSetting,
|
|
}));
|
|
|
|
function settingResponse(siteName = "Nezha") {
|
|
return {
|
|
...createSettingResponse(),
|
|
data: {
|
|
...createSettingResponse().data,
|
|
config: {
|
|
...createSettingResponse().data.config,
|
|
site_name: siteName,
|
|
},
|
|
},
|
|
};
|
|
}
|
|
|
|
function loginResponse() {
|
|
return {
|
|
success: true,
|
|
data: {
|
|
id: 1,
|
|
username: "admin",
|
|
password: "",
|
|
created_at: "2025-01-01T00:00:00.000Z",
|
|
updated_at: "2025-01-01T00:00:00.000Z",
|
|
},
|
|
};
|
|
}
|
|
|
|
function LocationProbe() {
|
|
const location = useLocation();
|
|
return <p>{location.pathname}</p>;
|
|
}
|
|
|
|
function renderHeader(route = "/server/1") {
|
|
return render(
|
|
<QueryClientProvider client={createTestQueryClient()}>
|
|
<MemoryRouter initialEntries={[route]}>
|
|
<ThemeProvider storageKey="header-theme-test">
|
|
<CommandProvider>
|
|
<Header />
|
|
<LocationProbe />
|
|
</CommandProvider>
|
|
</ThemeProvider>
|
|
</MemoryRouter>
|
|
</QueryClientProvider>,
|
|
);
|
|
}
|
|
|
|
function renderInRouter(ui: ReactElement) {
|
|
return render(<MemoryRouter>{ui}</MemoryRouter>);
|
|
}
|
|
|
|
describe("Header", () => {
|
|
beforeEach(() => {
|
|
headerMocks.backgroundImage = undefined;
|
|
headerMocks.connected = true;
|
|
headerMocks.fetchLoginUser.mockReset();
|
|
headerMocks.fetchSetting.mockReset();
|
|
headerMocks.lastMessage = new MessageEvent("message", {
|
|
data: JSON.stringify({ online: 4 }),
|
|
});
|
|
headerMocks.needReconnect = false;
|
|
headerMocks.setNeedReconnect.mockReset();
|
|
headerMocks.updateBackground.mockReset();
|
|
Object.assign(window, {
|
|
CustomBackgroundImage: "",
|
|
CustomDesc: "",
|
|
CustomLinks: "",
|
|
CustomLogo: "",
|
|
CustomMobileBackgroundImage: "",
|
|
});
|
|
headerMocks.fetchSetting.mockResolvedValue(settingResponse());
|
|
headerMocks.fetchLoginUser.mockRejectedValue(new Error("anonymous"));
|
|
});
|
|
|
|
it("renders configured site identity, custom links, online count, and dashboard state", async () => {
|
|
const user = userEvent.setup();
|
|
Object.assign(window, {
|
|
CustomDesc: "edge status",
|
|
CustomLinks: JSON.stringify([
|
|
{ link: "https://example.test", name: "Docs" },
|
|
]),
|
|
CustomLogo: "/logo.png",
|
|
});
|
|
Object.defineProperty(document, "cookie", {
|
|
configurable: true,
|
|
value: "session=1",
|
|
});
|
|
sessionStorage.setItem("selectedGroup", "Edge");
|
|
headerMocks.fetchSetting.mockResolvedValue(settingResponse("Status Hub"));
|
|
headerMocks.fetchLoginUser.mockResolvedValue(loginResponse());
|
|
|
|
renderHeader();
|
|
|
|
const siteName = await screen.findByText("Status Hub");
|
|
expect(screen.getByText("edge status")).toBeInTheDocument();
|
|
expect(screen.getByAltText("apple-touch-icon")).toHaveAttribute(
|
|
"src",
|
|
"/logo.png",
|
|
);
|
|
expect(screen.getAllByRole("link", { name: "Docs" })).toHaveLength(2);
|
|
expect(await screen.findAllByText("dashboard")).toHaveLength(2);
|
|
expect(screen.getByTestId("numeric-text")).toHaveTextContent("4");
|
|
expect(screen.getByText("online")).toBeInTheDocument();
|
|
|
|
await waitFor(() => {
|
|
expect(document.title).toBe("Status Hub");
|
|
});
|
|
expect(document.querySelector("link[rel='shortcut icon']")).toHaveAttribute(
|
|
"href",
|
|
"/logo.png",
|
|
);
|
|
|
|
await user.click(siteName);
|
|
|
|
expect(sessionStorage.getItem("selectedGroup")).toBeNull();
|
|
expect(screen.getByText("/")).toBeInTheDocument();
|
|
});
|
|
|
|
it("uses the offline display and login links when websocket and auth are unavailable", async () => {
|
|
headerMocks.connected = false;
|
|
|
|
const { container } = renderHeader();
|
|
|
|
expect(await screen.findAllByText("login")).toHaveLength(2);
|
|
expect(screen.getByText("offline")).toBeInTheDocument();
|
|
expect(
|
|
container.querySelector("[data-visible='true']"),
|
|
).toBeInTheDocument();
|
|
});
|
|
|
|
it("stores and removes the active custom background", async () => {
|
|
const user = userEvent.setup();
|
|
headerMocks.backgroundImage = "/desktop.png";
|
|
Object.assign(window, {
|
|
CustomBackgroundImage: "/desktop.png",
|
|
CustomMobileBackgroundImage: "/mobile.png",
|
|
});
|
|
|
|
const { container } = renderHeader();
|
|
await screen.findByText("Nezha");
|
|
|
|
const toggleButton = container
|
|
.querySelector(".lucide-image-minus")
|
|
?.closest("button");
|
|
expect(toggleButton).toBeInTheDocument();
|
|
|
|
await user.click(toggleButton as HTMLButtonElement);
|
|
|
|
expect(sessionStorage.getItem("savedBackgroundImage")).toBe("/desktop.png");
|
|
expect(headerMocks.updateBackground).toHaveBeenCalledWith(undefined);
|
|
});
|
|
|
|
it("restores the saved custom background", async () => {
|
|
const user = userEvent.setup();
|
|
sessionStorage.setItem("savedBackgroundImage", "/saved.png");
|
|
|
|
const { container } = renderHeader();
|
|
await screen.findByText("Nezha");
|
|
|
|
const toggleButton = container
|
|
.querySelector(".lucide-image-minus")
|
|
?.closest("button");
|
|
expect(toggleButton).toBeInTheDocument();
|
|
|
|
await user.click(toggleButton as HTMLButtonElement);
|
|
|
|
expect(headerMocks.updateBackground).toHaveBeenCalledWith("/saved.png");
|
|
});
|
|
});
|
|
|
|
describe("RefreshToast", () => {
|
|
beforeEach(() => {
|
|
headerMocks.needReconnect = false;
|
|
});
|
|
|
|
it("renders only while reconnect refresh is needed", () => {
|
|
vi.useFakeTimers();
|
|
sessionStorage.setItem("needRefresh", "true");
|
|
|
|
const { container, rerender } = renderInRouter(<RefreshToast />);
|
|
|
|
expect(container).toBeEmptyDOMElement();
|
|
|
|
headerMocks.needReconnect = true;
|
|
rerender(
|
|
<MemoryRouter>
|
|
<RefreshToast />
|
|
</MemoryRouter>,
|
|
);
|
|
|
|
expect(screen.getByText("refreshing...")).toBeInTheDocument();
|
|
expect(sessionStorage.getItem("needRefresh")).toBeNull();
|
|
});
|
|
});
|