import * as api from "@client/api"; import { useSettings } from "@client/hooks/useSettings"; import { render, screen, waitFor } from "@testing-library/react"; import type React from "react"; import { beforeEach, describe, expect, it, vi } from "vitest"; import { OnboardingGate } from "./OnboardingGate"; vi.mock("@client/api", () => ({ getDemoInfo: vi.fn(), validateLlm: vi.fn(), validateRxresume: vi.fn(), validateResumeConfig: vi.fn(), updateSettings: vi.fn(), })); vi.mock("@client/hooks/useSettings", () => ({ useSettings: vi.fn(), })); vi.mock("@client/pages/settings/components/SettingsInput", () => ({ SettingsInput: ({ label }: { label: string }) =>
{label}
, })); vi.mock("@client/pages/settings/components/BaseResumeSelection", () => ({ BaseResumeSelection: () =>
Base resume selection
, })); vi.mock("@/components/ui/alert-dialog", () => ({ AlertDialog: ({ children }: { children: React.ReactNode }) => (
{children}
), AlertDialogContent: ({ children }: { children: React.ReactNode }) => (
{children}
), AlertDialogDescription: ({ children }: { children: React.ReactNode }) => (
{children}
), AlertDialogHeader: ({ children }: { children: React.ReactNode }) => (
{children}
), AlertDialogTitle: ({ children }: { children: React.ReactNode }) => (
{children}
), })); vi.mock("@/components/ui/tabs", () => ({ Tabs: ({ children }: { children: React.ReactNode }) =>
{children}
, TabsContent: ({ children }: { children: React.ReactNode }) => (
{children}
), TabsList: ({ children }: { children: React.ReactNode }) => (
{children}
), TabsTrigger: ({ children }: { children: React.ReactNode }) => ( ), })); vi.mock("@/components/ui/select", () => ({ Select: ({ children }: { children: React.ReactNode }) => (
{children}
), SelectContent: ({ children }: { children: React.ReactNode }) => (
{children}
), SelectItem: ({ children }: { children: React.ReactNode }) => (
{children}
), SelectTrigger: ({ children }: { children: React.ReactNode }) => ( ), SelectValue: ({ children }: { children: React.ReactNode }) => ( {children} ), })); vi.mock("@/components/ui/progress", () => ({ Progress: () =>
Progress
, })); vi.mock("sonner", () => ({ toast: { error: vi.fn(), info: vi.fn(), success: vi.fn(), }, })); const settingsResponse = { settings: { llmProvider: "openrouter", llmApiKeyHint: null, rxresumeEmail: "", rxresumePasswordHint: null, rxresumeBaseResumeId: null, }, isLoading: false, refreshSettings: vi.fn(), }; describe("OnboardingGate", () => { beforeEach(() => { vi.clearAllMocks(); vi.mocked(api.getDemoInfo).mockResolvedValue({ demoMode: false, resetCadenceHours: 6, lastResetAt: null, nextResetAt: null, baselineVersion: null, baselineName: null, }); vi.mocked(useSettings).mockReturnValue(settingsResponse as any); }); it("renders the gate once validations complete and any fail", async () => { vi.mocked(api.validateLlm).mockResolvedValue({ valid: false, message: "Invalid", }); vi.mocked(api.validateRxresume).mockResolvedValue({ valid: true, message: null, }); vi.mocked(api.validateResumeConfig).mockResolvedValue({ valid: true, message: null, }); render(); await waitFor(() => expect(api.validateLlm).toHaveBeenCalled()); await waitFor(() => { expect(screen.getByText("Welcome to Job Ops")).toBeInTheDocument(); }); }); it("hides the gate when all validations succeed", async () => { vi.mocked(api.validateLlm).mockResolvedValue({ valid: true, message: null, }); vi.mocked(api.validateRxresume).mockResolvedValue({ valid: true, message: null, }); vi.mocked(api.validateResumeConfig).mockResolvedValue({ valid: true, message: null, }); render(); await waitFor(() => expect(api.validateLlm).toHaveBeenCalled()); expect(screen.queryByText("Welcome to Job Ops")).not.toBeInTheDocument(); }); it("skips LLM key validation for providers without API keys", async () => { vi.mocked(useSettings).mockReturnValue({ ...settingsResponse, settings: { ...settingsResponse.settings, llmProvider: "ollama", }, } as any); vi.mocked(api.validateRxresume).mockResolvedValue({ valid: false, message: "Missing", }); vi.mocked(api.validateResumeConfig).mockResolvedValue({ valid: true, message: null, }); render(); await waitFor(() => expect(api.validateRxresume).toHaveBeenCalled()); expect(api.validateLlm).not.toHaveBeenCalled(); expect(screen.getByText("Welcome to Job Ops")).toBeInTheDocument(); expect(screen.queryByText("LLM API key")).not.toBeInTheDocument(); }); });