158 lines
6.0 KiB
TypeScript
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();
|
|
});
|
|
});
|