/** * #22 — view anchoring/docking in `ProjectsView`. A view opened from the menu * starts floating (modal window); its header controls move it into the in-flow * left/right {@link DockRegion} and back, and the one-slot-per-view invariant * holds (docking a floating view removes the modal window). */ import { describe, it, expect } from "vitest"; import { render, screen, within, waitFor, fireEvent } from "@testing-library/react"; import { MockAgentGateway, MockGitGateway, MockProfileGateway, MockProjectGateway, MockSystemGateway, MockTemplateGateway, MockWorkStateGateway, } from "@/adapters/mock"; import type { Gateways } from "@/ports"; import { DIProvider } from "@/app/di"; import { ProjectsView } from "./ProjectsView"; async function renderWithProject() { const project = new MockProjectGateway(); await project.createProject("alpha", "/p/a"); const agentGateway = new MockAgentGateway(); const gateways = { system: new MockSystemGateway(), project, agent: agentGateway, profile: new MockProfileGateway(), template: new MockTemplateGateway(agentGateway), git: new MockGitGateway(), workState: new MockWorkStateGateway(), } as unknown as Gateways; render( , ); // Open the seeded project so panels have an active project to render for. fireEvent.click(await screen.findByRole("button", { name: "Open" })); await waitFor(() => expect(within(screen.getByRole("tablist")).getAllByRole("tab")).toHaveLength(1), ); } /** Opens a menu-bar dropdown item. */ function openMenuItem(menu: string, item: string) { fireEvent.click(screen.getByRole("button", { name: menu })); fireEvent.click(screen.getByRole("button", { name: item })); } describe("ProjectsView view docking (#22)", () => { it("opens a view floating, then docks it left as an in-flow column", async () => { await renderWithProject(); // View → Git opens a modal floating window (historical behaviour). openMenuItem("View", "Git"); const dialog = await screen.findByRole("dialog", { name: "Git" }); expect(dialog).toBeTruthy(); // Dock it to the left via the header control. fireEvent.click(within(dialog).getByRole("button", { name: "dock Git left" })); // The modal window is gone; the Git view now lives in the left dock column. await waitFor(() => expect(screen.queryByRole("dialog", { name: "Git" })).toBeNull(), ); const leftDock = screen.getByRole("complementary", { name: "left dock" }); expect(within(leftDock).getByLabelText("Git panel")).toBeTruthy(); // The main surface is still present beside the dock (in-flow, not overlay). expect(screen.getByRole("main")).toBeTruthy(); }); it("keeps one slot per view: docking right moves it off the left dock", async () => { await renderWithProject(); openMenuItem("View", "Git"); const dialog = await screen.findByRole("dialog", { name: "Git" }); fireEvent.click(within(dialog).getByRole("button", { name: "dock Git left" })); const leftDock = await screen.findByRole("complementary", { name: "left dock" }); const leftHeader = within(leftDock).getByLabelText("Git panel"); // Move it to the right dock from the docked header control. fireEvent.click( within(leftHeader).getByRole("button", { name: "dock Git right" }), ); await waitFor(() => expect(screen.queryByRole("complementary", { name: "left dock" })).toBeNull(), ); const rightDock = screen.getByRole("complementary", { name: "right dock" }); expect(within(rightDock).getByLabelText("Git panel")).toBeTruthy(); }); it("docks two different views side by side (left + right)", async () => { await renderWithProject(); // Dock Git left. openMenuItem("View", "Git"); fireEvent.click( within(await screen.findByRole("dialog", { name: "Git" })).getByRole( "button", { name: "dock Git left" }, ), ); // Dock Agents right. openMenuItem("View", "Agents"); fireEvent.click( within(await screen.findByRole("dialog", { name: "Agents" })).getByRole( "button", { name: "dock Agents right" }, ), ); const leftDock = await screen.findByRole("complementary", { name: "left dock" }); const rightDock = await screen.findByRole("complementary", { name: "right dock" }); expect(within(leftDock).getByLabelText("Git panel")).toBeTruthy(); expect(within(rightDock).getByLabelText("Agents panel")).toBeTruthy(); }); });