/** * @file PathPickButton.test.tsx * @description Unit tests for the Browse button beside path fields: a chosen * path fills the field, a Cancel changes nothing, a folder falls back to the * in-app browser on ANY native-picker failure, and a file falls back to a * "type the path" hint only when no picker exists for this browser. * @author Nguyễn Ngọc Trí Vĩ */ import { describe, it, expect, vi, beforeEach } from "vitest"; import { render, screen, waitFor } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; import { api } from "../../lib/api"; import { toast } from "../../lib/toast"; import { PathPickButton } from "../PathPickButton"; vi.mock("../../lib/api", () => ({ api: { lanes: { pickPath: vi.fn(), browse: vi.fn() } }, })); vi.mock("../../lib/toast", () => ({ toast: { info: vi.fn(), error: vi.fn(), success: vi.fn() }, })); const fail = (code: string) => Object.assign(new Error(code), { code }); beforeEach(() => { vi.mocked(api.lanes.pickPath).mockReset(); vi.mocked(api.lanes.browse) .mockReset() .mockResolvedValue({ path: "/tmp", parent: "/", entries: [] }); vi.mocked(toast.info).mockReset(); vi.mocked(toast.error).mockReset(); }); describe("PathPickButton", () => { it("fills the field with the chosen path and leaves it alone on Cancel", async () => { const onPick = vi.fn(); vi.mocked(api.lanes.pickPath).mockResolvedValueOnce({ path: "/home/u/proj" }); vi.mocked(api.lanes.pickPath).mockResolvedValueOnce({ cancelled: true }); render(); const button = screen.getByTitle("Browse for a folder"); await userEvent.click(button); await waitFor(() => expect(onPick).toHaveBeenCalledWith("/home/u/proj")); expect(api.lanes.pickPath).toHaveBeenCalledWith("dir", "/home/u"); await userEvent.click(button); await waitFor(() => expect(api.lanes.pickPath).toHaveBeenCalledTimes(2)); expect(onPick).toHaveBeenCalledTimes(1); }); it("opens the in-app folder browser on any failure, not only a missing picker", async () => { vi.mocked(api.lanes.pickPath).mockRejectedValue(fail("EBUSY")); render(); await userEvent.click(screen.getByTitle("Browse for a folder")); expect(await screen.findByRole("dialog", { name: "Browse for a folder" })).toBeInTheDocument(); expect(toast.error).not.toHaveBeenCalled(); }); it("hints to type a file path when no picker exists, and reports any other failure", async () => { vi.mocked(api.lanes.pickPath).mockRejectedValueOnce(fail("ENODIALOG")); vi.mocked(api.lanes.pickPath).mockRejectedValueOnce(fail("EDIALOG")); render(); const button = screen.getByRole("button"); await userEvent.click(button); await waitFor(() => expect(toast.info).toHaveBeenCalledTimes(1)); await userEvent.click(button); await waitFor(() => expect(toast.error).toHaveBeenCalledWith("EDIALOG")); expect(screen.queryByRole("dialog")).toBeNull(); }); });