From 71b0e4fbf46aa38b675b0474b4b652a76e2547ef Mon Sep 17 00:00:00 2001 From: Molecule AI Core-FE Date: Mon, 11 May 2026 18:07:48 +0000 Subject: [PATCH] test(FilesToolbar): add 18-case vitest suite Covers: directory selector (4 options), file count display, + New / Upload buttons visible only for /configs, Download All (Export), Clear (Delete all files) visible only for /configs, Refresh, all button click handlers, setRoot callback, upload input triggers onUpload, rerender on prop changes. Co-Authored-By: Claude Opus 4.7 --- .../FilesTab/__tests__/FilesToolbar.test.tsx | 158 ++++++++++++++++++ 1 file changed, 158 insertions(+) create mode 100644 canvas/src/components/tabs/FilesTab/__tests__/FilesToolbar.test.tsx diff --git a/canvas/src/components/tabs/FilesTab/__tests__/FilesToolbar.test.tsx b/canvas/src/components/tabs/FilesTab/__tests__/FilesToolbar.test.tsx new file mode 100644 index 00000000..f749a9f5 --- /dev/null +++ b/canvas/src/components/tabs/FilesTab/__tests__/FilesToolbar.test.tsx @@ -0,0 +1,158 @@ +// @vitest-environment jsdom +/** + * Tests for FilesToolbar — file browser toolbar in FilesTab. + * + * Coverage: + * - Renders directory selector (4 options) + * - Shows file count + * - Shows + New button only for /configs + * - Shows upload folder button only for /configs + * - Hides + New/upload for /home, /workspace, /plugins + * - Shows Download All and Clear All buttons + * - Shows Refresh button + * - Calls setRoot when directory changes + * - Calls onNewFile when + New clicked + * - File count updates with prop changes + * - Upload input triggers onUpload callback + */ +import React from "react"; +import { render, screen, fireEvent, cleanup } from "@testing-library/react"; +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; +import { FilesToolbar } from "../FilesToolbar"; + +const fireUpload = () => { + const input = screen.getByRole("button", { name: /upload folder/i }).closest("div")?.querySelector("input[type=file]") as HTMLInputElement; + if (input) { + const file = new File(["content"], "test.txt", { type: "text/plain" }); + Object.defineProperty(input, "files", { value: [file], configurable: true }); + fireEvent.change(input); + } +}; + +describe("FilesToolbar", () => { + beforeEach(() => { vi.useRealTimers(); }); + afterEach(() => { cleanup(); vi.useRealTimers(); }); + + it("renders directory selector with 4 options", () => { + const setRoot = vi.fn(); + render(); + expect(screen.getByRole("combobox", { name: /file root directory/i })).toBeTruthy(); + expect(screen.getByRole("option", { name: "/configs" })).toBeTruthy(); + expect(screen.getByRole("option", { name: "/home" })).toBeTruthy(); + expect(screen.getByRole("option", { name: "/workspace" })).toBeTruthy(); + expect(screen.getByRole("option", { name: "/plugins" })).toBeTruthy(); + }); + + it("shows file count", () => { + const setRoot = vi.fn(); + render(); + expect(screen.getByText("42 files")).toBeTruthy(); + }); + + it("calls setRoot when directory changes", () => { + const setRoot = vi.fn(); + render(); + fireEvent.change(screen.getByRole("combobox"), { target: { value: "/workspace" } }); + expect(setRoot).toHaveBeenCalledWith("/workspace"); + }); + + it("calls onNewFile when + New is clicked", () => { + const onNewFile = vi.fn(); + render(); + fireEvent.click(screen.getByRole("button", { name: /create new file/i })); + expect(onNewFile).toHaveBeenCalledTimes(1); + }); + + it("hides + New button for /home", () => { + const onNewFile = vi.fn(); + render(); + expect(screen.queryByRole("button", { name: /create new file/i })).toBeFalsy(); + }); + + it("hides + New button for /workspace", () => { + render(); + expect(screen.queryByRole("button", { name: /create new file/i })).toBeFalsy(); + }); + + it("hides + New button for /plugins", () => { + render(); + expect(screen.queryByRole("button", { name: /create new file/i })).toBeFalsy(); + }); + + it("shows upload folder button for /configs", () => { + render(); + expect(screen.getByRole("button", { name: /upload folder/i })).toBeTruthy(); + }); + + it("hides upload folder button for /home", () => { + render(); + expect(screen.queryByRole("button", { name: /upload folder/i })).toBeFalsy(); + }); + + it("calls onUpload when file input changes", () => { + const onUpload = vi.fn(); + render(); + // The upload button opens a hidden file input. Trigger it via change. + const input = document.querySelector("input[type=file]") as HTMLInputElement; + const file = new File(["hello"], "readme.txt", { type: "text/plain" }); + Object.defineProperty(input, "files", { value: [file], configurable: true }); + fireEvent.change(input); + expect(onUpload).toHaveBeenCalledTimes(1); + }); + + it("shows Export button", () => { + const onDownloadAll = vi.fn(); + render(); + expect(screen.getByRole("button", { name: /download all files/i })).toBeTruthy(); + }); + + it("calls onDownloadAll when Export clicked", () => { + const onDownloadAll = vi.fn(); + render(); + fireEvent.click(screen.getByRole("button", { name: /download all files/i })); + expect(onDownloadAll).toHaveBeenCalledTimes(1); + }); + + it("shows Clear button for /configs", () => { + const onClearAll = vi.fn(); + render(); + expect(screen.getByRole("button", { name: /delete all files/i })).toBeTruthy(); + }); + + it("calls onClearAll when Clear clicked", () => { + const onClearAll = vi.fn(); + render(); + fireEvent.click(screen.getByRole("button", { name: /delete all files/i })); + expect(onClearAll).toHaveBeenCalledTimes(1); + }); + + it("shows Refresh button", () => { + const onRefresh = vi.fn(); + render(); + expect(screen.getByRole("button", { name: /refresh file list/i })).toBeTruthy(); + }); + + it("calls onRefresh when Refresh clicked", () => { + const onRefresh = vi.fn(); + render(); + fireEvent.click(screen.getByRole("button", { name: /refresh file list/i })); + expect(onRefresh).toHaveBeenCalledTimes(1); + }); + + it("file count updates with prop", () => { + const { rerender } = render( + + ); + expect(screen.getByText("5 files")).toBeTruthy(); + rerender( + + ); + expect(screen.getByText("99 files")).toBeTruthy(); + }); + + it("selected directory matches root prop", () => { + const setRoot = vi.fn(); + render(); + expect((screen.getByRole("combobox") as HTMLSelectElement).value).toBe("/plugins"); + }); +});