WIP: Batch 4 — App, GatePanel, ReviewPanel frontend tests
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
158
frontend/src/App.test.tsx
Normal file
158
frontend/src/App.test.tsx
Normal file
@@ -0,0 +1,158 @@
|
||||
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: [] }),
|
||||
recordTests: vi.fn(),
|
||||
ensureAcceptance: vi.fn(),
|
||||
getReviewQueue: vi.fn(),
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
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(<App />);
|
||||
}
|
||||
|
||||
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();
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user