/** * @file PathPickButton.test.tsx * @description Unit tests for the Browse button beside path fields: a chosen * path fills the field, a Cancel changes nothing, and a folder or a file falls * back to the in-app browser on ANY native-picker failure (a LAN browser's * EREMOTE included). * @author Nguyễn Ngọc Trí Vĩ */ import { describe, it, expect, vi, beforeEach } from "vitest"; import { render, screen, waitFor, within } 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("opens the in-app browser for a file too, and a click on a file picks it", async () => { const onPick = vi.fn(); vi.mocked(api.lanes.pickPath).mockRejectedValue(fail("EREMOTE")); vi.mocked(api.lanes.browse).mockResolvedValue({ path: "/home/u/.ssh", parent: "/home/u", entries: [ { name: "id_ed25519", path: "/home/u/.ssh/id_ed25519", isGitRepo: false, file: true }, ], }); render(); await userEvent.click(screen.getByRole("button")); const dialog = await screen.findByRole("dialog", { name: "Browse for a file" }); expect(api.lanes.browse).toHaveBeenCalledWith("~/.ssh", true); expect(within(dialog).queryByText("Select this folder")).toBeNull(); await userEvent.click(await within(dialog).findByText("id_ed25519")); expect(onPick).toHaveBeenCalledWith("/home/u/.ssh/id_ed25519"); expect(screen.queryByRole("dialog")).toBeNull(); expect(toast.info).not.toHaveBeenCalled(); }); });