diff --git a/client/src/components/lanes/__tests__/LaneCard.test.tsx b/client/src/components/lanes/__tests__/LaneCard.test.tsx index 216e4ef..0140e1b 100644 --- a/client/src/components/lanes/__tests__/LaneCard.test.tsx +++ b/client/src/components/lanes/__tests__/LaneCard.test.tsx @@ -24,6 +24,9 @@ vi.mock("../../../lib/api", () => ({ down: vi.fn(), preflight: vi.fn().mockResolvedValue({ blocked: [], warnings: [] }), integration: vi.fn(), + agentsInstall: vi.fn(), + mcpSync: vi.fn(), + syncBaseCheck: vi.fn(), }, locks: { list: vi.fn(), @@ -364,3 +367,84 @@ describe("LaneCard — named locks", () => { expect(screen.queryByTestId("lane-locks-1")).not.toBeInTheDocument(); }); }); + +describe("agents install / mcp sync / integration badges / sync check", () => { + beforeEach(() => { + vi.mocked(api.lanes.runtime).mockResolvedValue({ + available: true as const, + provisioned: true as const, + up: false, + slot: 1, + profileDir: "/work/demo/.ccam/profile", + ports: {}, + }); + vi.mocked(api.lanes.integration).mockImplementation((_id, name) => + Promise.resolve({ enabled: name === "tracker" }) + ); + }); + + it("shows integration badges reflecting each toggle's state", async () => { + render(); + const tracker = await screen.findByTestId("lane-integration-tracker"); + const devQc = await screen.findByTestId("lane-integration-dev_qc"); + expect(tracker.className).toContain("status-success"); + expect(devQc.className).not.toContain("status-success"); + }); + + it("clicking Install agents calls the API and shows the result", async () => { + vi.mocked(api.lanes.agentsInstall).mockResolvedValue({ + installed: ["qc-local.md", "senior-gate-reviewer.md"], + }); + render(); + const button = await screen.findByTestId("lane-agents-install"); + await userEvent.click(button); + await waitFor(() => expect(api.lanes.agentsInstall).toHaveBeenCalledWith(1)); + expect(await screen.findByTestId("lane-action-result")).toHaveTextContent("qc-local.md"); + }); + + it("clicking Sync MCP calls the API and shows the result", async () => { + vi.mocked(api.lanes.mcpSync).mockResolvedValue({ + servers: ["playwright"], + profilesSeeded: [], + }); + render(); + const button = await screen.findByTestId("lane-mcp-sync"); + await userEvent.click(button); + await waitFor(() => expect(api.lanes.mcpSync).toHaveBeenCalledWith(1)); + expect(await screen.findByTestId("lane-action-result")).toHaveTextContent("playwright"); + }); + + it("clicking Check dev sync reports a clean result", async () => { + vi.mocked(api.lanes.syncBaseCheck).mockResolvedValue({ + code: 0, + devDelta: ["a.txt", "b.txt"], + overlap: [], + }); + render(); + const button = await screen.findByTestId("lane-sync-check"); + await userEvent.click(button); + await waitFor(() => expect(api.lanes.syncBaseCheck).toHaveBeenCalledWith(1)); + expect(await screen.findByTestId("lane-action-result")).toHaveTextContent("2"); + }); + + it("clicking Check dev sync reports a migration collision", async () => { + vi.mocked(api.lanes.syncBaseCheck).mockResolvedValue({ + code: 5, + collisions: [{ file: "002_a.sql", collidesWith: "002_b.sql", suggestion: "003_a.sql" }], + }); + render(); + const button = await screen.findByTestId("lane-sync-check"); + await userEvent.click(button); + expect(await screen.findByTestId("lane-action-result")).toHaveTextContent("003_a.sql"); + }); + + it("hides all four additions when the lane has no profile", async () => { + vi.mocked(api.lanes.runtime).mockResolvedValue({ available: false }); + render(); + await waitFor(() => expect(api.lanes.runtime).toHaveBeenCalled()); + expect(screen.queryByTestId("lane-agents-install")).toBeNull(); + expect(screen.queryByTestId("lane-mcp-sync")).toBeNull(); + expect(screen.queryByTestId("lane-sync-check")).toBeNull(); + expect(screen.queryByTestId("lane-integration-tracker")).toBeNull(); + }); +});