/** * #43, F3 — `ProjectsView` wires the `gitRepository` `when` signal to a real * check (`GitGateway.branches`), not a hardcoded `false` (Architect-mandated, * blocking for #43 closure). This exercises a plugin menu item declared * `when: "projectOpen && gitRepository"` end-to-end: disabled for a project * whose git check fails (not a repo), enabled for one whose check succeeds. */ import { describe, it, expect } from "vitest"; import { render, screen, waitFor, fireEvent } from "@testing-library/react"; import { MockAgentGateway, MockDesktopServerGateway, MockProfileGateway, MockProjectGateway, MockSystemGateway, MockTemplateGateway, MockWindowGateway, MockWorkStateGateway, } from "@/adapters/mock"; import type { GatewayError, GitBranches, GitCommit, GitFileStatus, GraphCommit } from "@/domain"; import type { Gateways, GitGateway } from "@/ports"; import { DIProvider } from "@/app/di"; import { PluginCommandRegistry, PluginLayoutRegistry, PluginMenuRegistry, PluginRuntimeRegistry, type LoadedPlugin, } from "@/plugins/runtime"; import { PluginRuntimeProvider } from "@/features/plugins"; import { ProjectsView } from "./ProjectsView"; /** Minimal `GitGateway` double whose `branches()` always rejects — "not a repo". */ class NonRepoGitGateway implements GitGateway { status(): Promise { return Promise.resolve([]); } stage(): Promise { return Promise.resolve(); } unstage(): Promise { return Promise.resolve(); } commit(): Promise { return Promise.reject({ code: "NOT_IMPLEMENTED", message: "n/a" } satisfies GatewayError); } branches(): Promise { const err: GatewayError = { code: "NOT_A_REPOSITORY", message: "not a git repository" }; return Promise.reject(err); } checkout(): Promise { return Promise.resolve(); } log(): Promise { return Promise.resolve([]); } init(): Promise { return Promise.resolve(); } graph(): Promise { return Promise.resolve([]); } } /** Minimal `GitGateway` double whose `branches()` always resolves — a real repo. */ class RealRepoGitGateway extends NonRepoGitGateway { override branches(): Promise { return Promise.resolve({ branches: ["main"], current: "main" }); } } function gitAwarePlugin(): LoadedPlugin { const contributes = { menus: [], menuItems: [ { id: "dev.acme.gitgraph.open", targetMenuId: "panels" as const, label: "Ouvrir le graphe Git", command: "dev.acme.gitgraph.open", when: "projectOpen && gitRepository", }, ], layouts: [], mcpServers: [], }; return { pluginId: "dev.acme.gitgraph", displayName: "Git Graph", contributes, commands: new PluginCommandRegistry( "dev.acme.gitgraph", new Set(["dev.acme.gitgraph.open"]), ), layouts: new PluginLayoutRegistry("dev.acme.gitgraph", new Set()), menu: new PluginMenuRegistry("dev.acme.gitgraph"), dispose: async () => {}, }; } function renderWithPlugin(git: GitGateway) { const agentGateway = new MockAgentGateway(); const gateways = { system: new MockSystemGateway(), project: new MockProjectGateway(), agent: agentGateway, profile: new MockProfileGateway(), template: new MockTemplateGateway(agentGateway), git, workState: new MockWorkStateGateway(), window: new MockWindowGateway(), desktopServer: new MockDesktopServerGateway(), } as unknown as Gateways; const registry = new PluginRuntimeRegistry(); registry.add(gitAwarePlugin()); return render( , ); } async function waitForIdle() { await waitFor(() => expect( (screen.getByRole("button", { name: "Refresh" }) as HTMLButtonElement).disabled, ).toBe(false), ); } async function createProject(name: string, root: string) { await waitForIdle(); fireEvent.change(screen.getByLabelText("project name"), { target: { value: name } }); fireEvent.change(screen.getByLabelText("project root"), { target: { value: root } }); fireEvent.click(screen.getByRole("button", { name: "Create project" })); } function panelsMenuItem(): HTMLButtonElement { return screen.getByRole("button", { name: /Ouvrir le graphe Git/ }) as HTMLButtonElement; } describe("ProjectsView — plugin `gitRepository` when-context wiring", () => { it("disables the item when the project's git check fails (not a repo)", async () => { renderWithPlugin(new NonRepoGitGateway()); await createProject("alpha", "/home/me/non-repo"); await screen.findByRole("tab"); fireEvent.click(screen.getByRole("button", { name: "Panneaux" })); await waitFor(() => { expect(panelsMenuItem().disabled).toBe(true); }); }); it("enables the item once the project's git check succeeds (real repo)", async () => { renderWithPlugin(new RealRepoGitGateway()); await createProject("beta", "/home/me/real-repo"); await screen.findByRole("tab"); fireEvent.click(screen.getByRole("button", { name: "Panneaux" })); await waitFor(() => { expect(panelsMenuItem().disabled).toBe(false); }); }); });