Files
AgentHub/client/src/components/__tests__/PathPickButton.test.tsx
T
nntrivi2001 46e3899945 fix: v2.7.4 — Changes matches JetBrains (#72), the chat says when Claude waits on a trust prompt (#73), Vietnamese Telex in Safari
- Changes (#72): a file git reports `MM` whose working copy equals HEAD again
  (a staged change since undone on disk) is no longer listed — JetBrains'
  GitChangesCollector drops it the same way. Mode-only changes stay listed.
  statusCounts (the dirty-tree safety checks) is unchanged.
- Chat (#73): Claude Code runs no hook before a folder is trusted, so a new
  pane's session has no row; the chat now says the session has not started,
  offers Open terminal, and asks before typing a message or /model //effort
  into the pane (an Enter would pick the trust prompt's "No, exit").
- Terminal: Safari + macOS's own Vietnamese Telex replaces the whole word on
  each key (insertReplacementText), which xterm.js ignores — only each word's
  first letter reached the pane. The edit is now sent as DELs + the new tail.
- Also: Ctrl+V pastes a screenshot into the terminal as a file path; the
  folder browser fallback lists files for a file field (`files=1`); the macOS
  native picker opens in front of the browser; a hidden tab is marked while a
  chat waits or once a turn ended; chat live line shows running calls, turn
  time and tokens, and "Done in …".
2026-10-12 15:40:51 +07:00

79 lines
3.4 KiB
TypeScript

/**
* @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ĩ <vinnt@smartgift.vn>
*/
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(<PathPickButton start=" /home/u " onPick={onPick} />);
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(<PathPickButton onPick={vi.fn()} />);
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(<PathPickButton kind="file" start="~/.ssh" onPick={onPick} />);
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();
});
});