import { render, screen, waitFor } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; import { beforeEach, describe, expect, it, vi } from "vitest"; import { api } from "./api/client"; vi.mock("./api/client", () => { const api = { getCurrentProject: vi.fn(), getKnownProjects: vi.fn(), getHomeDirectory: vi.fn(), openProject: vi.fn(), closeProject: vi.fn(), forgetKnownProject: vi.fn(), listDirectoryAbsolute: vi.fn(), getOllamaModels: vi.fn(), getAnthropicApiKeyExists: vi.fn(), getAnthropicModels: vi.fn(), getModelPreference: vi.fn(), setModelPreference: vi.fn(), cancelChat: vi.fn(), setAnthropicApiKey: vi.fn(), }; class ChatWebSocket { connect() {} close() {} sendChat() {} cancel() {} } return { api, ChatWebSocket }; }); vi.mock("./api/workflow", () => { return { workflowApi: { getAcceptance: vi.fn().mockResolvedValue({ can_accept: false, reasons: [], warning: null, summary: { total: 0, passed: 0, failed: 0 }, missing_categories: [], }), getReviewQueueAll: vi.fn().mockResolvedValue({ stories: [] }), getUpcomingStories: vi.fn().mockResolvedValue({ stories: [] }), recordTests: vi.fn(), ensureAcceptance: vi.fn(), getReviewQueue: vi.fn(), collectCoverage: vi.fn(), recordCoverage: vi.fn(), getStoryTodos: vi.fn().mockResolvedValue({ stories: [] }), }, }; }); const mockedApi = vi.mocked(api); describe("App", () => { beforeEach(() => { vi.resetModules(); vi.clearAllMocks(); mockedApi.getKnownProjects.mockResolvedValue([]); mockedApi.getHomeDirectory.mockResolvedValue("/home/user"); mockedApi.listDirectoryAbsolute.mockResolvedValue([]); mockedApi.getOllamaModels.mockResolvedValue([]); mockedApi.getAnthropicApiKeyExists.mockResolvedValue(false); mockedApi.getAnthropicModels.mockResolvedValue([]); mockedApi.getModelPreference.mockResolvedValue(null); }); async function renderApp() { const { default: App } = await import("./App"); return render(); } it("renders the selection screen when no project is open", async () => { await renderApp(); await waitFor(() => { expect( screen.getByPlaceholderText(/\/path\/to\/project/i), ).toBeInTheDocument(); }); }); it("populates path input with home directory", async () => { mockedApi.getHomeDirectory.mockResolvedValue("/Users/dave"); await renderApp(); await waitFor(() => { const input = screen.getByPlaceholderText( /\/path\/to\/project/i, ) as HTMLInputElement; expect(input.value).toBe("/Users/dave/"); }); }); it("opens project and shows chat view", async () => { mockedApi.openProject.mockResolvedValue("/home/user/myproject"); await renderApp(); await waitFor(() => { expect( screen.getByPlaceholderText(/\/path\/to\/project/i), ).toBeInTheDocument(); }); const input = screen.getByPlaceholderText( /\/path\/to\/project/i, ) as HTMLInputElement; await userEvent.clear(input); await userEvent.type(input, "/home/user/myproject"); const openButton = screen.getByRole("button", { name: /open project/i }); await userEvent.click(openButton); await waitFor(() => { expect(mockedApi.openProject).toHaveBeenCalledWith( "/home/user/myproject", ); }); }); it("shows error when openProject fails", async () => { mockedApi.openProject.mockRejectedValue(new Error("Path does not exist")); await renderApp(); await waitFor(() => { expect( screen.getByPlaceholderText(/\/path\/to\/project/i), ).toBeInTheDocument(); }); const input = screen.getByPlaceholderText( /\/path\/to\/project/i, ) as HTMLInputElement; await userEvent.clear(input); await userEvent.type(input, "/bad/path"); const openButton = screen.getByRole("button", { name: /open project/i }); await userEvent.click(openButton); await waitFor(() => { expect(screen.getByText(/Path does not exist/)).toBeInTheDocument(); }); }); it("shows known projects list", async () => { mockedApi.getKnownProjects.mockResolvedValue([ "/home/user/project1", "/home/user/project2", ]); await renderApp(); await waitFor(() => { expect(screen.getByTitle("/home/user/project1")).toBeInTheDocument(); expect(screen.getByTitle("/home/user/project2")).toBeInTheDocument(); }); }); });