import { fireEvent, render, screen, waitFor, within } from "@testing-library/react"; import type { ReactElement } from "react"; import { beforeEach, describe, expect, it, vi } from "vitest"; import { DIProvider } from "@/app/di"; import { createMockGateways } from "@/adapters/mock"; import type { Gateways } from "@/ports"; import type { ProjectFileListing } from "@/domain"; import { ProjectFilesPanel } from "./ProjectFilesPanel"; import { WebWorkspace } from "./WebWorkspace"; function deferred() { let resolve!: (value: T | PromiseLike) => void; const promise = new Promise((res) => { resolve = res; }); return { promise, resolve }; } function listing(path: string, entries: ProjectFileListing["entries"]): ProjectFileListing { return { path, entries }; } beforeEach(() => { vi.restoreAllMocks(); window.innerWidth = 1024; window.dispatchEvent(new Event("resize")); }); function renderWithGateways(gateways: Gateways, element: ReactElement) { return render({element}); } describe("ProjectFilesPanel", () => { it("navigates progressively and keeps download actions explicit", async () => { const gateways = createMockGateways(); const listProjectFiles = vi.fn(async (_projectId: string, path: string) => { if (path === "src") return listing("src", []); return listing("", [ { name: "src", path: "src", kind: "directory", modifiedAtMs: 1_786_780_000_000, }, { name: "README.md", path: "README.md", kind: "file", sizeBytes: 2048, modifiedAtMs: 1_786_780_010_000, }, ]); }); gateways.project.listProjectFiles = listProjectFiles; gateways.project.downloadProjectFile = vi.fn(async () => {}); gateways.project.downloadProjectDirectory = vi.fn(async () => {}); renderWithGateways(gateways, ); expect(await screen.findByText("README.md")).toBeTruthy(); fireEvent.click(screen.getByRole("button", { name: "Ouvrir" })); await waitFor(() => expect(listProjectFiles).toHaveBeenLastCalledWith("p1", "src")); expect(await screen.findByText("Ce dossier est vide.")).toBeTruthy(); expect(screen.getByRole("button", { name: "src" })).toBeTruthy(); expect(gateways.project.downloadProjectDirectory).not.toHaveBeenCalled(); }); it("downloads files and folders through separate row actions with a preparation state", async () => { const gateways = createMockGateways(); const fileDownload = deferred(); gateways.project.listProjectFiles = vi.fn(async () => listing("", [ { name: "docs", path: "docs", kind: "directory" }, { name: "README.md", path: "README.md", kind: "file", sizeBytes: 1024 }, ]), ); gateways.project.downloadProjectFile = vi.fn(() => fileDownload.promise); gateways.project.downloadProjectDirectory = vi.fn(async () => {}); renderWithGateways(gateways, ); const list = await screen.findByRole("list", { name: "Fichiers du projet" }); const readmeRow = within(list).getByText("README.md").closest("li"); const docsRow = within(list).getByText("docs").closest("li"); expect(readmeRow).toBeTruthy(); expect(docsRow).toBeTruthy(); fireEvent.click(within(readmeRow!).getByRole("button", { name: "Télécharger" })); expect(await screen.findByText("Préparation du téléchargement…")).toBeTruthy(); expect(gateways.project.downloadProjectFile).toHaveBeenCalledWith("p1", "README.md"); expect(gateways.project.downloadProjectDirectory).not.toHaveBeenCalled(); fileDownload.resolve(); await waitFor(() => expect(screen.queryByText("Préparation du téléchargement…")).toBeNull(), ); fireEvent.click( within(docsRow!).getByRole("button", { name: "Télécharger le dossier" }), ); expect(gateways.project.downloadProjectDirectory).toHaveBeenCalledWith("p1", "docs"); }); it("surfaces list errors", async () => { const gateways = createMockGateways(); gateways.project.listProjectFiles = vi.fn(async () => { throw { code: "INVALID", message: "path outside project" }; }); renderWithGateways(gateways, ); expect((await screen.findByRole("alert")).textContent).toContain("path outside project"); }); }); describe("WebWorkspace files integration", () => { async function setupWorkspace() { const gateways = createMockGateways(); await gateways.project.createProject("IdeA", "/srv/idea"); gateways.project.listProjectFiles = vi.fn(async () => listing("", [{ name: "README.md", path: "README.md", kind: "file", sizeBytes: 512 }]), ); gateways.project.downloadProjectFile = vi.fn(async () => {}); gateways.project.downloadProjectDirectory = vi.fn(async () => {}); return gateways; } it("opens Fichiers from the desktop Panneaux menu", async () => { const gateways = await setupWorkspace(); renderWithGateways(gateways, ); fireEvent.click(await screen.findByText("IdeA")); fireEvent.click(await screen.findByRole("button", { name: "Panneaux" })); fireEvent.click(await screen.findByRole("button", { name: "Fichiers" })); fireEvent.click(await screen.findByRole("button", { name: "Flottant" })); expect(await screen.findByTestId("project-files-panel")).toBeTruthy(); expect(screen.getByText("README.md")).toBeTruthy(); }); it("exposes Fichiers as a first-level mobile destination", async () => { window.innerWidth = 390; window.dispatchEvent(new Event("resize")); const gateways = await setupWorkspace(); renderWithGateways(gateways, ); fireEvent.click(await screen.findByText("IdeA")); fireEvent.click(await screen.findByRole("button", { name: "Fichiers" })); expect(screen.getByTestId("web-workspace").getAttribute("data-shell")).toBe( "mobile-task-flow", ); expect(await screen.findByTestId("project-files-panel")).toBeTruthy(); }); });