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: () =>
dash-command
, })); vi.mock("../components/Footer", () => ({ default: () => , })); vi.mock("../components/Header", () => ({ default: () =>
header
, RefreshToast: () =>
refresh-toast
, })); vi.mock("../pages/Server", () => ({ default: ({ backendError }: { backendError?: Error | null }) => (
server-page
{backendError &&

{backendError.message}

}
), })); vi.mock("../pages/ServerDetail", () => ({ default: () =>
server-detail-page
, })); 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( , ), }; } 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("[style]")).some( (element) => element.style.backgroundImage.includes("/desktop.png"), ), ).toBe(true); expect( Array.from(container.querySelectorAll("[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(""), ); renderApp(); await waitFor(() => { expect(appMocks.injectContext).toHaveBeenCalledWith( "", ); }); 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(); }); });