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();
+ });
+});