import { describe, expect, it, vi } from "vitest"; import { renderToStaticMarkup } from "react-dom/server"; // Stub out next/navigation. The settings sections that read it (Account) // only call `useRouter().push()` from inside an effect after mount, so a // no-op stub is enough for SSR smoke rendering. vi.mock("next/navigation", () => ({ useRouter: () => ({ push: vi.fn(), replace: vi.fn() }), usePathname: () => "/settings/account", })); // Stub TanStack Query — every section reads from it but we only need the // SSR markup, not live data. Returning a stable empty payload keeps the // "loading" branch from spinning forever and lets the section title render. // The About section reads `["api", "version"]` and gates the body on data // presence — return a representative payload so the version card paints. vi.mock("@tanstack/react-query", () => ({ useQuery: ({ queryKey }: { queryKey: unknown[] }) => { if ( Array.isArray(queryKey) && queryKey[0] === "api" && queryKey[1] === "version" ) { return { data: { version: "1.4.0", buildSha: "abc1234567", builtAt: "2026-05-08T12:00:00Z", license: "AGPL-3.0", repository: "https://github.com/MBombeck/HealthLog", changelog: "https://github.com/MBombeck/HealthLog/blob/main/CHANGELOG.md", docs: "https://docs.healthlog.dev", }, isLoading: false, refetch: vi.fn(), }; } return { data: null, isLoading: false, refetch: vi.fn() }; }, useQueryClient: () => ({ invalidateQueries: vi.fn() }), useMutation: () => ({ mutate: vi.fn(), isPending: false }), })); // Stub the auth hook — sections check `isAuthenticated` to enable queries. // We return `isAuthenticated: true` + a minimal user so the loading-spinner // branch in AccountSection paints the real form. vi.mock("@/hooks/use-auth", () => ({ useAuth: () => ({ user: { id: "u1", username: "marc", email: "marc@example.com", heightCm: 180, dateOfBirth: "1990-01-01", gender: "MALE", role: "USER", }, isAuthenticated: true, isLoading: false, refetch: vi.fn(), }), })); // Sub-sections of Dashboard own complex data fetching that's already // covered by their own tests. Stub at the import boundary. vi.mock("@/components/settings/dashboard-layout-section", () => ({ DashboardLayoutSection: () =>
, })); vi.mock("@/components/settings/thresholds-section", () => ({ ThresholdsSection: () =>
, })); import { I18nProvider } from "@/lib/i18n/context"; import { AboutSection } from "../about-section"; import { AccountSection } from "../account-section"; import { AdvancedSection } from "../advanced-section"; import { AiSection } from "../ai-section"; import { ApiSection } from "../api-section"; import { DashboardSection } from "../dashboard-section"; import { IntegrationsSection } from "../integrations-section"; import { NotificationsSection } from "../notifications-section"; function render(node: React.ReactElement, locale: "en" | "de" = "en") { return renderToStaticMarkup( {node}, ); } describe("settings sections — SSR smoke", () => { it(" renders the Account heading and Profile card", () => { const html = render(); // Section title resolves via the i18n provider. expect(html).toContain("Account"); // Profile card heading is also painted. expect(html).toContain("Profile"); // No raw key leaks past the i18n layer. expect(html).not.toContain("settings.sections."); }); it(" renders version + license + check-for-updates button", () => { const html = render(); expect(html).toContain("About"); // The new about.* keys resolve. expect(html).toContain("Check for updates"); expect(html).not.toContain("settings.about."); }); it(" renders the AI Insights card", () => { const html = render(); expect(html).toContain("AI Insights"); }); it(" renders Withings card title", () => { const html = render(); expect(html).toContain("Integrations"); expect(html).toContain("Withings"); }); it(" renders heading", () => { const html = render(); expect(html).toContain("Notifications"); }); it(" renders heading and embeds the layout customizer", () => { // v1.4.3 split: the per-metric thresholds (Persönliche Zielwerte) moved // out into their own settings section under `/settings/thresholds`, // so the dashboard panel is now layout-only. Verifying the heading // says "Dashboard" (not "Übersicht" — the v1.4.2 placeholder) and the // layout slot still mounts. const html = render(); expect(html).toContain("Dashboard"); expect(html).toContain('data-testid="dashboard-layout"'); expect(html).not.toContain('data-testid="thresholds"'); }); it(" renders endpoints + tokens cards", () => { const html = render(); expect(html).toContain("API & Tokens"); }); it(" renders export + danger-zone cards", () => { const html = render(); expect(html).toContain("Advanced"); }); it(" resolves about.* keys in German", () => { const html = render(, "de"); expect(html).toContain("Auf Updates prüfen"); }); });