From f759ec74b34ecb360473aae8ee04814f699c4d1c Mon Sep 17 00:00:00 2001 From: Jan Leon Date: Tue, 23 Jun 2026 14:57:57 +0200 Subject: [PATCH] test(dashboard): add smoke test for home client dashboard (#4793) Smoke test guarding the dashboard home client render (regression #4745/#4759). Code fix already landed via #4761; this PR's jsdom smoke test is the net-new regression guard. Integrated into release/v3.8.35. --- ...-page-client-dashboard-smoke-4615.test.tsx | 161 ++++++++++++++++++ 1 file changed, 161 insertions(+) create mode 100644 tests/unit/ui/home-page-client-dashboard-smoke-4615.test.tsx diff --git a/tests/unit/ui/home-page-client-dashboard-smoke-4615.test.tsx b/tests/unit/ui/home-page-client-dashboard-smoke-4615.test.tsx new file mode 100644 index 0000000000..bc9cad86d3 --- /dev/null +++ b/tests/unit/ui/home-page-client-dashboard-smoke-4615.test.tsx @@ -0,0 +1,161 @@ +// @vitest-environment jsdom +import React from "react"; +import { act } from "react"; +import { createRoot } from "react-dom/client"; +import { afterEach, beforeEach, expect, it, vi } from "vitest"; + +function makeTranslator() { + const t = (key: string) => key; + t.rich = (key: string) => key; + return t; +} + +vi.mock("next-intl", () => ({ + useTranslations: () => makeTranslator(), +})); + +vi.mock("next/navigation", () => ({ + useRouter: () => ({ + push: vi.fn(), + replace: vi.fn(), + refresh: vi.fn(), + }), +})); + +vi.mock("next/link", () => ({ + default: ({ href, children, ...props }: React.AnchorHTMLAttributes) => ( + + {children} + + ), +})); + +vi.mock("next/dynamic", () => ({ + default: () => + function DynamicStub() { + return
; + }, +})); + +vi.mock("@/shared/components", () => ({ + Card: ({ children }: { children: React.ReactNode }) =>
{children}
, + CardSkeleton: () =>
, + Button: ({ + children, + loading: _loading, + fullWidth: _fullWidth, + variant: _variant, + size: _size, + ...props + }: React.ButtonHTMLAttributes & { + loading?: boolean; + fullWidth?: boolean; + variant?: string; + size?: string; + }) => , + Modal: ({ children, isOpen }: { children: React.ReactNode; isOpen: boolean }) => + isOpen ?
{children}
: null, +})); + +vi.mock("@/shared/components/ProviderIcon", () => ({ + default: () => , +})); + +const notifyMock = { + success: vi.fn(), + error: vi.fn(), + addNotification: vi.fn(), +}; + +function useNotificationStoreMock() { + return notifyMock; +} +useNotificationStoreMock.getState = () => notifyMock; + +vi.mock("@/store/notificationStore", () => ({ + useNotificationStore: useNotificationStoreMock, +})); + +vi.mock("@/shared/hooks/useElectron", () => ({ + useIsElectron: () => false, + useOpenExternal: () => ({ openExternal: vi.fn() }), +})); + +vi.mock("@/shared/utils/clipboard", () => ({ + copyToClipboard: vi.fn(async () => undefined), +})); + +const { default: HomePageClient } = + await import("../../../src/app/(dashboard)/dashboard/HomePageClient"); + +function jsonResponse(body: unknown) { + return { + ok: true, + json: async () => body, + } as Response; +} + +let container: HTMLDivElement; +let root: ReturnType; + +beforeEach(() => { + ( + globalThis as typeof globalThis & { IS_REACT_ACT_ENVIRONMENT?: boolean } + ).IS_REACT_ACT_ENVIRONMENT = true; + + vi.stubGlobal( + "fetch", + vi.fn((input: RequestInfo | URL) => { + const url = String(input); + if (url === "/api/settings") { + return Promise.resolve( + jsonResponse({ + showQuickStartOnHome: true, + showProviderTopologyOnHome: false, + }) + ); + } + if (url === "/api/providers") { + return Promise.resolve(jsonResponse({ connections: [] })); + } + if (url === "/api/models") { + return Promise.resolve(jsonResponse({ models: [] })); + } + if (url === "/api/system/version") { + return Promise.resolve( + jsonResponse({ + current: "0.0.0-test", + latest: "0.0.0-test", + updateAvailable: false, + channel: "test", + autoUpdateSupported: false, + }) + ); + } + if (url === "/api/provider-nodes") { + return Promise.resolve(jsonResponse({ nodes: [] })); + } + return Promise.resolve(jsonResponse({})); + }) + ); + + container = document.createElement("div"); + document.body.appendChild(container); + root = createRoot(container); +}); + +afterEach(() => { + act(() => root.unmount()); + container.remove(); + vi.unstubAllGlobals(); + vi.clearAllMocks(); +}); + +it("renders the dashboard home client without throwing an internal server error", async () => { + await act(async () => { + root.render(); + }); + + expect(container.textContent).not.toContain("Internal Server Error"); + expect(container.textContent).toContain("quickStart"); +});