import { beforeEach, describe, it, expect } from "vitest"; import { fireEvent, render, screen, within } from "@testing-library/react"; import type { Gateways } from "@/ports"; import { DIProvider } from "@/app/di"; import { createMockGateways, MockWorkStateGateway } from "@/adapters/mock"; import { WebSession } from "@/adapters/http"; import type { FetchLike } from "@/adapters/http/httpInvoker"; import type { FlagStore } from "@/adapters/http/webSession"; import { WebApp } from "./WebApp"; function memStore(): FlagStore { const map = new Map(); return { getItem: (k) => map.get(k) ?? null, setItem: (k, v) => void map.set(k, v), removeItem: (k) => void map.delete(k), }; } const okFetch: FetchLike = async () => ({ ok: true, status: 200, json: async () => ({ ok: true }), text: async () => "{}", }); async function seededGateways(): Promise { const gateways = createMockGateways(); const project = await gateways.project.createProject("Demo", "/srv/demo"); (gateways.workState as MockWorkStateGateway)._setProjectWorkState(project.id, { agents: [ { agentId: "a1", name: "Archi", profileId: "p1", busy: { state: "idle" }, tickets: [] }, ], conversations: [], }); return gateways; } function renderPairedWebApp(gateways: Gateways) { const session = new WebSession({ baseUrl: "https://h", fetchImpl: okFetch, store: memStore() }); session.markPaired(); return render( , ); } beforeEach(() => { window.localStorage.clear(); window.innerWidth = 1280; window.dispatchEvent(new Event("resize")); }); describe("WebWorkspace desktop layout", () => { it("mounts the desktop AppImage-style shell with LayoutTabs/LayoutGrid and no detached action", async () => { renderPairedWebApp(await seededGateways()); expect( (await screen.findByTestId("web-workspace")).getAttribute("data-shell"), ).toBe( "desktop-layout", ); expect(screen.queryByTestId("web-mobile-task-canvas")).toBeNull(); fireEvent.click(await screen.findByText("Demo")); expect(await screen.findByTestId("layout-tabs")).toBeTruthy(); expect(await screen.findByTestId("layout-grid")).toBeTruthy(); expect(screen.getByLabelText("right dock")).toBeTruthy(); expect(screen.queryByRole("button", { name: /detach/i })).toBeNull(); expect(screen.queryByText(/FenĂȘtre/)).toBeNull(); }); it("opens panels as floating or docked from the desktop menu bar without a detached web mode", async () => { renderPairedWebApp(await seededGateways()); fireEvent.click(await screen.findByText("Demo")); await screen.findByTestId("layout-grid"); fireEvent.click(screen.getByRole("button", { name: "Panneaux" })); const menu = await screen.findByRole("menu", { name: "Panneaux" }); fireEvent.click(within(menu).getByRole("button", { name: "Tickets" })); const submenu = await screen.findByRole("menu", { name: "Tickets" }); fireEvent.click(within(submenu).getByRole("button", { name: "Flottant" })); expect(await screen.findByRole("dialog", { name: "Tickets" })).toBeTruthy(); expect(screen.queryByRole("button", { name: /detached/i })).toBeNull(); }); it("omits Projects and Project context from Panneaux while keeping project access", async () => { renderPairedWebApp(await seededGateways()); fireEvent.click(await screen.findByText("Demo")); await screen.findByTestId("layout-grid"); fireEvent.click(screen.getByRole("button", { name: "Panneaux" })); const menu = await screen.findByRole("menu", { name: "Panneaux" }); expect(within(menu).queryByRole("button", { name: "Contexte projet" })).toBeNull(); expect(within(menu).queryByRole("button", { name: "Projets" })).toBeNull(); expect(screen.getByRole("button", { name: "Projets" })).toBeTruthy(); }); });