diff --git a/canvas/src/components/tabs/FilesTab/__tests__/FilesToolbar.test.tsx b/canvas/src/components/tabs/FilesTab/__tests__/FilesToolbar.test.tsx deleted file mode 100644 index cb23c95d..00000000 --- a/canvas/src/components/tabs/FilesTab/__tests__/FilesToolbar.test.tsx +++ /dev/null @@ -1,349 +0,0 @@ -// @vitest-environment jsdom -/** - * Tests for FilesToolbar — the top-of-panel bar for the Files tab. - * Covers: directory select, file count, New/Upload/Clear (configs-only), - * Export, Refresh, and aria-labels. - */ -import React from "react"; -import { render, screen, fireEvent, cleanup } from "@testing-library/react"; -import { afterEach, describe, expect, it, vi } from "vitest"; -import { FilesToolbar } from "../FilesToolbar"; - -afterEach(cleanup); - -describe("FilesToolbar", () => { - describe("renders base toolbar", () => { - it("renders the directory select with aria-label", () => { - render( - - ); - expect( - screen.getByRole("combobox", { name: /file root directory/i }) - ).toBeTruthy(); - }); - - it("renders the file count", () => { - render( - - ); - expect(screen.getByText("7 files")).toBeTruthy(); - }); - - it("renders Export button", () => { - render( - - ); - expect( - screen.getByRole("button", { name: /download all files/i }) - ).toBeTruthy(); - }); - - it("renders Refresh button", () => { - render( - - ); - expect(screen.getByRole("button", { name: /refresh file list/i })).toBeTruthy(); - }); - - it("renders 0 files when count is 0", () => { - render( - - ); - expect(screen.getByText("0 files")).toBeTruthy(); - }); - }); - - describe("configs-only buttons", () => { - it("shows New and Upload buttons when root is /configs", () => { - render( - - ); - expect( - screen.getByRole("button", { name: /create new file/i }) - ).toBeTruthy(); - expect( - screen.getByRole("button", { name: /upload folder/i }) - ).toBeTruthy(); - expect(screen.getByRole("button", { name: /delete all files/i })).toBeTruthy(); - }); - - it("hides New and Upload when root is /workspace", () => { - render( - - ); - expect( - screen.queryByRole("button", { name: /create new file/i }) - ).toBeNull(); - expect( - screen.queryByRole("button", { name: /upload folder/i }) - ).toBeNull(); - expect( - screen.queryByRole("button", { name: /delete all files/i }) - ).toBeNull(); - // Export and Refresh are still present - expect( - screen.getByRole("button", { name: /download all files/i }) - ).toBeTruthy(); - }); - - it("hides New and Upload when root is /home", () => { - render( - - ); - expect( - screen.queryByRole("button", { name: /create new file/i }) - ).toBeNull(); - expect( - screen.queryByRole("button", { name: /upload folder/i }) - ).toBeNull(); - }); - - it("hides New and Upload when root is /plugins", () => { - render( - - ); - expect( - screen.queryByRole("button", { name: /create new file/i }) - ).toBeNull(); - expect( - screen.queryByRole("button", { name: /upload folder/i }) - ).toBeNull(); - }); - }); - - describe("callbacks", () => { - it("calls setRoot when directory is changed", () => { - const setRoot = vi.fn(); - render( - - ); - fireEvent.change(screen.getByRole("combobox"), { - target: { value: "/workspace" }, - }); - expect(setRoot).toHaveBeenCalledWith("/workspace"); - }); - - it("calls onNewFile when New button is clicked", () => { - const onNewFile = vi.fn(); - render( - - ); - fireEvent.click(screen.getByRole("button", { name: /create new file/i })); - expect(onNewFile).toHaveBeenCalledTimes(1); - }); - - it("calls onDownloadAll when Export button is clicked", () => { - const onDownloadAll = vi.fn(); - render( - - ); - fireEvent.click(screen.getByRole("button", { name: /download all files/i })); - expect(onDownloadAll).toHaveBeenCalledTimes(1); - }); - - it("calls onClearAll when Clear button is clicked", () => { - const onClearAll = vi.fn(); - render( - - ); - fireEvent.click(screen.getByRole("button", { name: /delete all files/i })); - expect(onClearAll).toHaveBeenCalledTimes(1); - }); - - it("calls onRefresh when Refresh button is clicked", () => { - const onRefresh = vi.fn(); - render( - - ); - fireEvent.click(screen.getByRole("button", { name: /refresh file list/i })); - expect(onRefresh).toHaveBeenCalledTimes(1); - }); - - it("calls onUpload when the hidden file input changes", () => { - const onUpload = vi.fn(); - render( - - ); - // Find the hidden file input - const fileInput = document.querySelector( - 'input[type="file"]' - ) as HTMLInputElement; - expect(fileInput).toBeTruthy(); - expect(fileInput?.getAttribute("aria-label")).toBe("Upload folder files"); - }); - }); - - describe("a11y", () => { - it("all buttons have aria-label or accessible name", () => { - render( - - ); - // All buttons should be findable by role - const buttons = screen.getAllByRole("button"); - for (const btn of buttons) { - expect(btn.getAttribute("aria-label") ?? btn.textContent).toBeTruthy(); - } - }); - - it("directory select has aria-label", () => { - render( - - ); - const select = screen.getByRole("combobox"); - expect(select.getAttribute("aria-label")).toBe("File root directory"); - }); - }); -}); diff --git a/canvas/src/components/tabs/FilesTab/__tests__/NotAvailablePanel.test.tsx b/canvas/src/components/tabs/FilesTab/__tests__/NotAvailablePanel.test.tsx deleted file mode 100644 index c670bb50..00000000 --- a/canvas/src/components/tabs/FilesTab/__tests__/NotAvailablePanel.test.tsx +++ /dev/null @@ -1,101 +0,0 @@ -// @vitest-environment jsdom -/** - * Tests for NotAvailablePanel — the full-tab placeholder shown when a - * workspace's runtime doesn't own a platform-managed filesystem (today: - * runtime === "external"). Covers rendering, a11y, and runtime prop - * display. - */ -import React from "react"; -import { render, screen, cleanup } from "@testing-library/react"; -import { afterEach, describe, expect, it } from "vitest"; -import { NotAvailablePanel } from "../NotAvailablePanel"; - -afterEach(cleanup); - -describe("NotAvailablePanel", () => { - describe("renders", () => { - it("renders the heading", () => { - render(); - expect(screen.getByText("Files not available")).toBeTruthy(); - }); - - it("renders the description text", () => { - render(); - expect( - screen.getByText(/whose filesystem isn't owned by the platform/i) - ).toBeTruthy(); - }); - - it("displays the runtime name in the description", () => { - render(); - // The runtime name appears inside the paragraph - const para = screen.getByText(/whose filesystem isn't owned/i); - expect(para.textContent).toContain("aws-lambda"); - }); - - it("renders the SVG folder icon with aria-hidden", () => { - render(); - const svg = document.querySelector("svg"); - expect(svg).toBeTruthy(); - expect(svg?.getAttribute("aria-hidden")).toBe("true"); - }); - - it("uses the provided runtime prop verbatim", () => { - render(); - const monoRuntime = document.querySelector(".font-mono"); - expect(monoRuntime?.textContent).toBe("cloud-run"); - }); - - it("renders the 'Use the Chat tab' guidance text", () => { - render(); - expect(screen.getByText(/Use the Chat tab/i)).toBeTruthy(); - }); - - it("is contained in a full-height flex column", () => { - render(); - const container = screen.getByText("Files not available").closest("div"); - expect(container?.className).toContain("flex"); - expect(container?.className).toContain("flex-col"); - expect(container?.className).toContain("items-center"); - expect(container?.className).toContain("justify-center"); - expect(container?.className).toContain("h-full"); - }); - }); - - describe("a11y", () => { - it("heading is an h3", () => { - render(); - expect(screen.getByRole("heading", { level: 3 })).toBeTruthy(); - }); - - it("SVG icon has aria-hidden so screen readers skip it", () => { - render(); - const svg = document.querySelector("svg"); - expect(svg?.getAttribute("aria-hidden")).toBe("true"); - }); - - it("description paragraph is present with descriptive text", () => { - render(); - const paras = document.querySelectorAll("p"); - expect(paras.length).toBeGreaterThan(0); - const text = Array.from(paras) - .map((p) => p.textContent) - .join(" "); - expect(text.toLowerCase()).toContain("runtime"); - }); - }); - - describe("props", () => { - it("renders with a short runtime name", () => { - render(); - const monoRuntime = document.querySelector(".font-mono"); - expect(monoRuntime?.textContent).toBe("ext"); - }); - - it("renders with a complex runtime name", () => { - render(); - const monoRuntime = document.querySelector(".font-mono"); - expect(monoRuntime?.textContent).toBe("gcp-cloud-functions-v2"); - }); - }); -});