mirror of
https://github.com/Buriburizaem0n/nezha-dash-v1.git
synced 2026-11-05 01:10:20 +00:00
191 lines
5.5 KiB
TypeScript
191 lines
5.5 KiB
TypeScript
import { QueryClientProvider } from "@tanstack/react-query";
|
|
import { render, screen, waitFor } from "@testing-library/react";
|
|
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
import App from "@/App";
|
|
import { createTestQueryClient } from "@/test/utils";
|
|
|
|
const appMocks = vi.hoisted(() => ({
|
|
backgroundImage: undefined as string | undefined,
|
|
fetchSetting: vi.fn(),
|
|
injectContext: vi.fn(),
|
|
setTheme: vi.fn(),
|
|
}));
|
|
|
|
vi.mock("../components/DashCommand", () => ({
|
|
DashCommand: () => <div>dash-command</div>,
|
|
}));
|
|
|
|
vi.mock("../components/Footer", () => ({
|
|
default: () => <footer>footer</footer>,
|
|
}));
|
|
|
|
vi.mock("../components/Header", () => ({
|
|
default: () => <header>header</header>,
|
|
RefreshToast: () => <div>refresh-toast</div>,
|
|
}));
|
|
|
|
vi.mock("../pages/Server", () => ({
|
|
default: ({ backendError }: { backendError?: Error | null }) => (
|
|
<div>
|
|
<div>server-page</div>
|
|
{backendError && <p>{backendError.message}</p>}
|
|
</div>
|
|
),
|
|
}));
|
|
|
|
vi.mock("../pages/ServerDetail", () => ({
|
|
default: () => <div>server-detail-page</div>,
|
|
}));
|
|
|
|
vi.mock("../hooks/use-background", () => ({
|
|
useBackground: () => ({ backgroundImage: appMocks.backgroundImage }),
|
|
}));
|
|
|
|
vi.mock("../hooks/use-theme", () => ({
|
|
useTheme: () => ({ setTheme: appMocks.setTheme }),
|
|
}));
|
|
|
|
vi.mock("../lib/inject", () => ({
|
|
InjectContext: appMocks.injectContext,
|
|
}));
|
|
|
|
vi.mock("../lib/nezha-api", () => ({
|
|
fetchSetting: appMocks.fetchSetting,
|
|
}));
|
|
|
|
function settingResponse(customCode = "") {
|
|
return {
|
|
success: true,
|
|
data: {
|
|
config: {
|
|
debug: false,
|
|
language: "zh-CN",
|
|
site_name: "Nezha",
|
|
user_template: "",
|
|
admin_template: "",
|
|
custom_code: customCode,
|
|
},
|
|
version: "1.0.0",
|
|
},
|
|
};
|
|
}
|
|
|
|
function renderApp(route = "/") {
|
|
window.history.pushState({}, "", route);
|
|
const queryClient = createTestQueryClient();
|
|
|
|
return {
|
|
queryClient,
|
|
...render(
|
|
<QueryClientProvider client={queryClient}>
|
|
<App />
|
|
</QueryClientProvider>,
|
|
),
|
|
};
|
|
}
|
|
|
|
describe("App", () => {
|
|
beforeEach(() => {
|
|
appMocks.backgroundImage = undefined;
|
|
appMocks.fetchSetting.mockResolvedValue(settingResponse());
|
|
appMocks.injectContext.mockResolvedValue(undefined);
|
|
});
|
|
|
|
it("renders the main shell after settings load and applies global theme/background settings", async () => {
|
|
Object.assign(window, {
|
|
ForceTheme: "dark",
|
|
CustomMobileBackgroundImage: "/mobile.png",
|
|
});
|
|
appMocks.backgroundImage = "/desktop.png";
|
|
|
|
const { container } = renderApp();
|
|
|
|
expect(await screen.findByText("server-page")).toBeInTheDocument();
|
|
expect(screen.getByText("refresh-toast")).toBeInTheDocument();
|
|
expect(screen.getByText("header")).toBeInTheDocument();
|
|
expect(screen.getByText("dash-command")).toBeInTheDocument();
|
|
expect(screen.getByText("footer")).toBeInTheDocument();
|
|
expect(appMocks.setTheme).toHaveBeenCalledWith("dark");
|
|
expect(
|
|
Array.from(container.querySelectorAll<HTMLElement>("[style]")).some(
|
|
(element) => element.style.backgroundImage.includes("/desktop.png"),
|
|
),
|
|
).toBe(true);
|
|
expect(
|
|
Array.from(container.querySelectorAll<HTMLElement>("[style]")).some(
|
|
(element) => element.style.backgroundImage.includes("/mobile.png"),
|
|
),
|
|
).toBe(true);
|
|
});
|
|
|
|
it("renders the app shell while initial settings are still pending", async () => {
|
|
appMocks.fetchSetting.mockImplementation(
|
|
() => new Promise(() => undefined),
|
|
);
|
|
|
|
renderApp();
|
|
|
|
expect(await screen.findByText("server-page")).toBeInTheDocument();
|
|
expect(screen.getByText("refresh-toast")).toBeInTheDocument();
|
|
expect(screen.getByText("header")).toBeInTheDocument();
|
|
expect(screen.getByText("dash-command")).toBeInTheDocument();
|
|
expect(screen.getByText("footer")).toBeInTheDocument();
|
|
});
|
|
|
|
it("injects custom code before showing the app shell", async () => {
|
|
appMocks.fetchSetting.mockResolvedValue(
|
|
settingResponse("<script>custom</script>"),
|
|
);
|
|
|
|
renderApp();
|
|
|
|
await waitFor(() => {
|
|
expect(appMocks.injectContext).toHaveBeenCalledWith(
|
|
"<script>custom</script>",
|
|
);
|
|
});
|
|
expect(await screen.findByText("server-page")).toBeInTheDocument();
|
|
});
|
|
|
|
it("renders the app shell when initial settings fetch fails", async () => {
|
|
appMocks.fetchSetting.mockRejectedValue(new Error("settings failed"));
|
|
|
|
renderApp();
|
|
|
|
expect(await screen.findByText("server-page")).toBeInTheDocument();
|
|
expect(screen.getByText("refresh-toast")).toBeInTheDocument();
|
|
expect(screen.getByText("header")).toBeInTheDocument();
|
|
expect(screen.getByText("dash-command")).toBeInTheDocument();
|
|
expect(screen.getByText("footer")).toBeInTheDocument();
|
|
expect(await screen.findByText("settings failed")).toBeInTheDocument();
|
|
});
|
|
|
|
it("keeps rendering with stale settings when a later settings refetch fails", async () => {
|
|
let requestCount = 0;
|
|
appMocks.fetchSetting.mockImplementation(() => {
|
|
requestCount += 1;
|
|
return requestCount === 1
|
|
? Promise.resolve(settingResponse())
|
|
: Promise.reject(new Error("settings failed"));
|
|
});
|
|
|
|
const { queryClient } = renderApp();
|
|
|
|
expect(await screen.findByText("server-page")).toBeInTheDocument();
|
|
|
|
await queryClient.refetchQueries({ queryKey: ["setting"] });
|
|
|
|
await waitFor(() => {
|
|
expect(appMocks.fetchSetting.mock.calls.length).toBeGreaterThanOrEqual(2);
|
|
});
|
|
expect(screen.getByText("server-page")).toBeInTheDocument();
|
|
expect(screen.queryByText("settings failed")).not.toBeInTheDocument();
|
|
});
|
|
|
|
it("routes server detail paths through the app router", async () => {
|
|
renderApp("/server/42");
|
|
|
|
expect(await screen.findByText("server-detail-page")).toBeInTheDocument();
|
|
});
|
|
});
|