import { describe, it, expect, vi, beforeEach } from "vitest"; import { render, screen } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; import { FileBrowser } from "../FileBrowser.impl"; import type { DirectoryListing, MonitoringMachine } from "../../types"; // usePersistentState (browserState) reads/writes localStorage; clear between tests // so the selectedPath / currentDir state never leaks across cases. beforeEach(() => { window.localStorage.clear(); }); function machineFixture( overrides: Partial = {}, ): MonitoringMachine { return { id: "local", name: "Local", mode: "local", enabled: true, services: ["files", "monitoring"], host: "", port: 22, username: "", key_directory: "", key_name: "", ssh_key_id: "", ssh_private_key_set: false, ssh_private_key_passphrase_set: false, password_set: false, media_root: "", path_prefix: "", jellyfin_url: "", jellyfin_user_id: "", jellyfin_api_key_set: false, jellyseerr_url: "", jellyseerr_api_key_set: false, notes: "", ...overrides, }; } function listingFixture( entries: { name: string; type: string; size: number; mtime: number; }[], ): DirectoryListing { return { path: "/", entries, count: entries.length }; } let listing: DirectoryListing; let machines: MonitoringMachine[]; vi.mock("react-router-dom", () => ({ useSearchParams: () => [new URLSearchParams(), vi.fn()], useNavigate: () => vi.fn(), })); vi.mock("../../hooks/useFiles", () => ({ useDirectoryListing: () => ({ data: listing, isLoading: false, error: null, refetch: vi.fn(), }), useFfprobe: () => ({ data: undefined, isLoading: false, error: null }), useJobTemplates: () => ({ data: [] }), useRunJob: () => ({ isPending: false, mutate: vi.fn(), data: undefined }), })); vi.mock("../../hooks/useSettings", () => ({ useMonitoringSettings: () => ({ data: machines }), })); beforeEach(() => { machines = [machineFixture()]; listing = listingFixture([ { name: "movies", type: "d", size: 0, mtime: 1_700_000_000 }, { name: "video.mkv", type: "f", size: 1_500_000_000, mtime: 1_700_000_000 }, { name: "notes.txt", type: "f", size: 12, mtime: 1_700_000_000 }, ]); }); describe("FileBrowser (slice 7a — TanStack DataTable parity)", () => { it("renders the 5 locked columns (type/name/ext/size/modified)", () => { render(); const headers = screen .getAllByRole("columnheader") .map((h) => h.textContent); // The leading selection column header is empty (checkbox); the 5 data // columns are Type, Name, Ext, Size, Modified in that order. expect(headers).toEqual( expect.arrayContaining(["Type", "Name", "Ext", "Size", "Modified"]), ); expect(headers.filter((h) => h === "Type").length).toBe(1); expect(headers.filter((h) => h === "Modified").length).toBe(1); }); it("clicking a file row selects it for ffprobe preview (Media info)", async () => { render(); // The selected-file path surfaces in the Browser status caption once chosen. expect(screen.queryByText(/Selected: \/video\.mkv/)).toBeNull(); await userEvent.click(screen.getByText("video.mkv")); expect(screen.getByText(/Selected: \/video\.mkv/)).toBeInTheDocument(); // A recognized video file enters the ffprobe branch; with empty ffprobe // data it shows the "No ffprobe data available." status (proving the // selected file routed into the Media info preview flow). expect(screen.getByText("No ffprobe data available.")).toBeInTheDocument(); }); it("clicking a directory row navigates into it (no ffprobe selection)", async () => { render(); await userEvent.click(screen.getByText("movies")); // After navigating into /movies, the status caption shows the new cwd and // NO "Selected:" segment (directories are opened, not selected for preview). expect(screen.getByText(/Current: \/movies\b/)).toBeInTheDocument(); expect(screen.queryByText(/Selected:/)).toBeNull(); }); });