Files
IdeA/frontend/src/features/web/ProjectFilesPanel.test.tsx

158 lines
6.0 KiB
TypeScript

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<T>() {
let resolve!: (value: T | PromiseLike<T>) => void;
const promise = new Promise<T>((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(<DIProvider gateways={gateways}>{element}</DIProvider>);
}
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, <ProjectFilesPanel projectId="p1" />);
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<void>();
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, <ProjectFilesPanel projectId="p1" />);
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, <ProjectFilesPanel projectId="p1" />);
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, <WebWorkspace />);
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, <WebWorkspace />);
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();
});
});