Files
fusion/packages/dashboard/app/components/FileBrowserModal.test.tsx
gsxdsm 9fa8f5f223 feat(FN-801): show hidden files in workspace file listings
- Remove dotfile filtering from file-service so hidden files appear in workspace browser
- Update FileBrowserModal and TaskDetailModal UI to display hidden file entries
- Add UI/hook tests for hidden file entries in FileBrowserModal and useWorkspaceFileBrowser
- Add file-service unit tests for listing, reading, and filtering hidden files
- Update dashboard styles for hidden file display
- Document Files feature section in README
2026-04-03 18:50:05 -07:00

192 lines
5.6 KiB
TypeScript

import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
import { render, screen, fireEvent, waitFor } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { FileBrowserModal } from "./FileBrowserModal";
import * as workspaceBrowserHook from "../hooks/useWorkspaceFileBrowser";
import * as workspaceEditorHook from "../hooks/useWorkspaceFileEditor";
import * as workspacesHook from "../hooks/useWorkspaces";
vi.mock("../hooks/useWorkspaceFileBrowser");
vi.mock("../hooks/useWorkspaceFileEditor");
vi.mock("../hooks/useWorkspaces");
const mockUseWorkspaceFileBrowser = vi.mocked(workspaceBrowserHook.useWorkspaceFileBrowser);
const mockUseWorkspaceFileEditor = vi.mocked(workspaceEditorHook.useWorkspaceFileEditor);
const mockUseWorkspaces = vi.mocked(workspacesHook.useWorkspaces);
describe("FileBrowserModal", () => {
const mockOnClose = vi.fn();
const mockOnWorkspaceChange = vi.fn();
const mockSave = vi.fn().mockResolvedValue(undefined);
const mockSetContent = vi.fn();
const mockSetPath = vi.fn();
const mockRefresh = vi.fn();
const defaultBrowserState = {
entries: [
{ name: "file1.ts", type: "file" as const, size: 1024, mtime: "2024-01-01" },
{ name: "folder1", type: "directory" as const, mtime: "2024-01-01" },
],
currentPath: ".",
setPath: mockSetPath,
loading: false,
error: null,
refresh: mockRefresh,
};
const defaultEditorState = {
content: "console.log('hello');",
setContent: mockSetContent,
originalContent: "console.log('hello');",
loading: false,
saving: false,
error: null,
save: mockSave,
hasChanges: false,
mtime: "2024-01-01",
};
beforeEach(() => {
vi.resetAllMocks();
mockUseWorkspaceFileBrowser.mockReturnValue(defaultBrowserState);
mockUseWorkspaceFileEditor.mockReturnValue(defaultEditorState);
mockUseWorkspaces.mockReturnValue({
projectName: "kb",
workspaces: [
{ id: "FN-001", label: "FN-001", title: "Task One", worktree: "/repo/.worktrees/kb-001", kind: "task" },
{ id: "FN-002", label: "FN-002", title: "Task Two", worktree: "/repo/.worktrees/kb-002", kind: "task" },
],
loading: false,
error: null,
});
Object.defineProperty(window, "innerWidth", {
writable: true,
configurable: true,
value: 1024,
});
});
afterEach(() => {
vi.restoreAllMocks();
});
it("renders project-root modal title and workspace selector", () => {
render(
<FileBrowserModal
initialWorkspace="project"
isOpen={true}
onClose={mockOnClose}
onWorkspaceChange={mockOnWorkspaceChange}
/>,
);
expect(screen.getByText("Files — Project")).toBeInTheDocument();
expect(screen.getByRole("button", { name: /kb/i })).toBeInTheDocument();
expect(mockUseWorkspaceFileBrowser).toHaveBeenCalledWith("project", true);
});
it("opens a file in the editor when selected", async () => {
render(
<FileBrowserModal
initialWorkspace="project"
isOpen={true}
onClose={mockOnClose}
/>,
);
fireEvent.click(screen.getByText("file1.ts"));
await waitFor(() => {
expect(screen.getByLabelText("Editor for file1.ts")).toBeInTheDocument();
});
expect(mockUseWorkspaceFileEditor).toHaveBeenLastCalledWith("project", "file1.ts", true);
});
it("switches workspace and notifies parent", async () => {
const user = userEvent.setup();
render(
<FileBrowserModal
initialWorkspace="project"
isOpen={true}
onClose={mockOnClose}
onWorkspaceChange={mockOnWorkspaceChange}
/>,
);
await user.click(screen.getByRole("button", { name: /kb/i }));
await user.click(screen.getByRole("button", { name: /FN-002 Task Two/i }));
expect(mockOnWorkspaceChange).toHaveBeenCalledWith("FN-002");
});
it("shows back button in mobile editor view", async () => {
Object.defineProperty(window, "innerWidth", {
writable: true,
configurable: true,
value: 375,
});
render(
<FileBrowserModal
initialWorkspace="project"
isOpen={true}
onClose={mockOnClose}
/>,
);
fireEvent(window, new Event("resize"));
fireEvent.click(screen.getByText("file1.ts"));
await waitFor(() => {
expect(screen.getByLabelText("Back to file list")).toBeInTheDocument();
});
});
it("closes on Escape and saves on Cmd+S", () => {
mockUseWorkspaceFileEditor.mockReturnValue({
...defaultEditorState,
hasChanges: true,
});
render(
<FileBrowserModal
initialWorkspace="project"
isOpen={true}
onClose={mockOnClose}
/>,
);
fireEvent.keyDown(document, { key: "Escape" });
fireEvent.keyDown(document, { key: "s", metaKey: true });
expect(mockOnClose).toHaveBeenCalledTimes(1);
expect(mockSave).toHaveBeenCalledTimes(1);
});
it("renders hidden files and directories from the file listing", () => {
mockUseWorkspaceFileBrowser.mockReturnValue({
...defaultBrowserState,
entries: [
{ name: ".env.example", type: "file", size: 42, mtime: "2024-01-01" },
{ name: ".github", type: "directory", mtime: "2024-01-01" },
{ name: "src", type: "directory", mtime: "2024-01-01" },
],
});
render(
<FileBrowserModal
initialWorkspace="project"
isOpen={true}
onClose={mockOnClose}
/>,
);
expect(screen.getByText(".env.example")).toBeInTheDocument();
expect(screen.getByText(".github")).toBeInTheDocument();
expect(screen.getByText("src")).toBeInTheDocument();
});
});