forked from bots-garden/ori
| ✨ Workspace panel, selectors, previews, desktop app, sandbox template, resizable file tree, light/dark theme | 1 | import { fireEvent, render, screen, waitFor } from "@testing-library/react"; |
| 2 | import { beforeEach, describe, expect, it, vi } from "vitest"; | |
| 3 | import type { FileEntry } from "../api"; | |
| 4 | import { resetWorkspace, useWorkspace } from "../workspace"; | |
| 5 | import { FileTree } from "./FileTree"; | |
| 6 | ||
| 7 | const { listFilesMock } = vi.hoisted(() => ({ listFilesMock: vi.fn() })); | |
| 8 | ||
| 9 | vi.mock("../api", () => ({ | |
| 10 | listFiles: listFilesMock, | |
| 11 | })); | |
| 12 | ||
| 13 | const rootEntries: FileEntry[] = [ | |
| 14 | { name: "src", path: "/w/src", isDir: true, size: 0 }, | |
| 15 | { name: "README.md", path: "/w/README.md", isDir: false, size: 7 }, | |
| 16 | ]; | |
| 17 | ||
| 18 | describe("FileTree", () => { | |
| 19 | beforeEach(() => { | |
| 20 | resetWorkspace(); | |
| 21 | listFilesMock.mockReset(); | |
| 22 | }); | |
| 23 | ||
| 24 | it("lists the workspace root", async () => { | |
| 25 | listFilesMock.mockResolvedValue({ path: "/w", entries: rootEntries }); | |
| 26 | render(<FileTree />); | |
| 27 | ||
| 28 | await waitFor(() => expect(screen.getByText("README.md")).toBeDefined()); | |
| 29 | expect(screen.getByText(/src/)).toBeDefined(); | |
| 30 | expect(listFilesMock.mock.calls[0]).toEqual([]); | |
| 31 | }); | |
| 32 | ||
| 33 | it("expands a directory lazily", async () => { | |
| 34 | listFilesMock.mockImplementation((path?: string) => | |
| 35 | Promise.resolve( | |
| 36 | path === "/w/src" | |
| 37 | ? { | |
| 38 | path: "/w/src", | |
| 39 | entries: [ | |
| 40 | { | |
| 41 | name: "main.go", | |
| 42 | path: "/w/src/main.go", | |
| 43 | isDir: false, | |
| 44 | size: 3, | |
| 45 | }, | |
| 46 | ], | |
| 47 | } | |
| 48 | : { path: "/w", entries: rootEntries }, | |
| 49 | ), | |
| 50 | ); | |
| 51 | render(<FileTree />); | |
| 52 | await waitFor(() => expect(screen.getByText(/src/)).toBeDefined()); | |
| 53 | ||
| 54 | fireEvent.click(screen.getByText(/src/)); | |
| 55 | await waitFor(() => expect(screen.getByText("main.go")).toBeDefined()); | |
| 56 | expect(listFilesMock).toHaveBeenCalledWith("/w/src"); | |
| 57 | }); | |
| 58 | ||
| 59 | it("opens a file in preview on click and in editor on double-click", async () => { | |
| 60 | listFilesMock.mockResolvedValue({ path: "/w", entries: rootEntries }); | |
| 61 | render(<FileTree />); | |
| 62 | await waitFor(() => expect(screen.getByText("README.md")).toBeDefined()); | |
| 63 | ||
| 64 | fireEvent.click(screen.getByText("README.md")); | |
| 65 | expect(useWorkspace.getState()).toMatchObject({ | |
| 66 | tab: "preview", | |
| 67 | previewPath: "/w/README.md", | |
| 68 | }); | |
| 69 | ||
| 70 | fireEvent.dblClick(screen.getByText("README.md")); | |
| 71 | expect(useWorkspace.getState()).toMatchObject({ | |
| 72 | tab: "editor", | |
| 73 | editorPath: "/w/README.md", | |
| 74 | }); | |
| 75 | }); | |
| 76 | ||
| 77 | it("shows Material icons for files and directories", async () => { | |
| 78 | listFilesMock.mockResolvedValue({ path: "/w", entries: rootEntries }); | |
| 79 | const { container } = render(<FileTree />); | |
| 80 | await waitFor(() => expect(screen.getByText("README.md")).toBeDefined()); | |
| 81 | ||
| 82 | const icons = Array.from( | |
| 83 | container.querySelectorAll("img.filetree-icon"), | |
| 84 | ).map((img) => img.getAttribute("src")); | |
| 85 | expect(icons).toHaveLength(2); | |
| 86 | for (const src of icons) { | |
| 87 | expect(src).toMatch(/^\/material-icons\/.+\.svg$/); | |
| 88 | } | |
| 89 | }); | |
| 90 | ||
| 91 | it("highlights the file open in the preview", async () => { | |
| 92 | listFilesMock.mockResolvedValue({ path: "/w", entries: rootEntries }); | |
| 93 | const { container } = render(<FileTree />); | |
| 94 | await waitFor(() => expect(screen.getByText("README.md")).toBeDefined()); | |
| 95 | ||
| 96 | expect(container.querySelector(".filetree-active")).toBeNull(); | |
| 97 | fireEvent.click(screen.getByText("README.md")); | |
| 98 | await waitFor(() => | |
| 99 | expect(container.querySelector(".filetree-active")).not.toBeNull(), | |
| 100 | ); | |
| 101 | }); | |
| 102 | ||
| 103 | it("surfaces API errors", async () => { | |
| 104 | listFilesMock.mockRejectedValue(new Error("permission denied")); | |
| 105 | render(<FileTree />); | |
| 106 | await waitFor(() => | |
| 107 | expect(screen.getByText("permission denied")).toBeDefined(), | |
| 108 | ); | |
| 109 | }); | |
| 110 | }); |