import React from "react"; import "@testing-library/jest-dom"; import { fireEvent, render, screen, waitFor } from "@testing-library/react"; import AiWorkspacePanel from "./components/AiWorkspacePanel"; import Markdown from "./components/Markdown"; import { ToastProvider } from "./toast"; import { api } from "./api"; import { AccountPlanProvider } from "./accountPlan"; jest.mock("./api", () => ({ api: { get: jest.fn(), post: jest.fn(), delete: jest.fn(), interceptors: { request: { use: jest.fn() }, response: { use: jest.fn() } }, }, getApiErrorMessage: (_e: any, fallback?: string) => fallback || "Request failed.", })); const mockedApi = api as jest.Mocked; function renderPanel() { return render( , ); } beforeEach(() => { jest.clearAllMocks(); mockedApi.get.mockImplementation((url: string) => { if (url.includes("/modules")) return Promise.resolve({ data: { modules: [], provider: "gemini" } } as any); if (url === "/ai/usage") return Promise.resolve({ data: { currentMonth: { calls: 3, inputCharacters: 100, outputCharacters: 50, estimatedTokens: 38 }, allTime: { calls: 8, inputCharacters: 300, outputCharacters: 120, estimatedTokens: 105 } } } as any); return Promise.resolve({ data: [] } as any); // history }); }); test("shows transparent monthly usage", async () => { renderPanel(); expect(await screen.findByText(/approximately/)).toHaveTextContent("3 runs"); expect(screen.getByText(/approximately/)).toHaveTextContent("38 tokens"); }); test("renders modules and generates a suggestion into history", async () => { mockedApi.post.mockResolvedValueOnce({ data: { id: 1, module: "job-analysis", mode: null, title: "Job analysis", provider: "gemini", result: { text: "**Company**\nAcme" }, createdAtUtc: new Date().toISOString() }, } as any); renderPanel(); fireEvent.click(await screen.findByRole("button", { name: /Generate/i })); await waitFor(() => expect(mockedApi.post).toHaveBeenCalledWith("/jobapplications/7/ai/generate", expect.objectContaining({ module: "job-analysis" }))); expect(await screen.findByText("Acme")).toBeInTheDocument(); }); test("cover letter sends the selected mode", async () => { mockedApi.post.mockResolvedValueOnce({ data: { id: 2, module: "cover-letter", mode: "professional", title: "Cover letter ยท Professional", provider: "p", result: { text: "Dear team" }, createdAtUtc: new Date().toISOString() }, } as any); renderPanel(); fireEvent.click(await screen.findByText("Cover Letter")); fireEvent.click(screen.getByRole("button", { name: /Generate/i })); await waitFor(() => expect(mockedApi.post).toHaveBeenCalledWith("/jobapplications/7/ai/generate", expect.objectContaining({ module: "cover-letter", mode: "professional" }))); }); test("free users see a locked state and cannot start generation", async () => { render( , ); expect(await screen.findByText(/AI generation is a Pro feature/)).toBeInTheDocument(); expect(screen.getByRole("button", { name: "Pro required" })).toBeDisabled(); expect(mockedApi.post).not.toHaveBeenCalled(); }); test("Markdown renders headings, bold, and bullet lists", () => { render(); expect(screen.getByText("Title")).toBeInTheDocument(); expect(screen.getByText("Strengths")).toBeInTheDocument(); expect(screen.getByText("one")).toBeInTheDocument(); expect(screen.getByText("two")).toBeInTheDocument(); });