/** * @file ChatAddMenu.test.tsx * @description The `+` menu's existing-resource pickers: Project files (a * search with folders, attached as `@path`), the workspace's Context files * (files only, none missing) and this browser's Recent uploads. * @author Nguyễn Ngọc Trí Vĩ */ import { fireEvent, render, screen } from "@testing-library/react"; import { MemoryRouter } from "react-router-dom"; import { beforeEach, describe, expect, it, vi } from "vitest"; import i18n from "i18next"; import { ChatAddMenu } from "../ChatAddMenu"; import { api } from "../../../lib/api"; import { rememberUploads } from "../../../lib/promptAttach"; vi.mock("../../../lib/api", async (importOriginal) => { const actual = await importOriginal>(); return { ...actual, api: { run: { files: vi.fn().mockResolvedValue({ items: ["src/", "src/a.ts"] }) }, lanes: { sources: vi.fn().mockResolvedValue({ sources: [ { id: 1, kind: "file", path: "/data/ctx/1/brief.md", name: "brief.md", missing: false }, { id: 2, kind: "file", path: "/data/ctx/1/gone.md", name: "gone.md", missing: true }, { id: 3, kind: "folder", path: "/srv/docs", name: "docs", missing: false }, ], }), }, ccConfig: { skills: vi.fn().mockResolvedValue({ items: [] }), plugins: vi.fn().mockResolvedValue({ plugins: [] }), }, }, }; }); function renderMenu(laneId: number | null = 7) { const onAttach = vi.fn(); render( {}} onInsert={() => {}} onAttach={onAttach} /> ); fireEvent.click(screen.getByTestId("chat-add")); return onAttach; } beforeEach(() => { i18n.changeLanguage("en"); localStorage.clear(); vi.clearAllMocks(); }); describe("ChatAddMenu — existing resources", () => { it("searches the project, folders included, and attaches a pick as @path", async () => { const onAttach = renderMenu(); fireEvent.mouseEnter(screen.getByTestId("chat-add-project")); fireEvent.click(await screen.findByTestId("chat-add-project-src/")); expect(api.run.files).toHaveBeenCalledWith("/repo", "", true); expect(onAttach).toHaveBeenCalledWith([{ path: "@src/", name: "src/" }]); }); it("offers the workspace's context files that are still there, files only", async () => { const onAttach = renderMenu(); fireEvent.mouseEnter(screen.getByTestId("chat-add-context")); fireEvent.click(await screen.findByTestId("chat-add-context-brief.md")); expect(api.lanes.sources).toHaveBeenCalledWith(7); expect(screen.queryByTestId("chat-add-context-gone.md")).toBeNull(); expect(onAttach).toHaveBeenCalledWith([{ path: "@/data/ctx/1/brief.md", name: "brief.md" }]); }); it("has no Context files without a workspace", () => { renderMenu(null); expect(screen.queryByTestId("chat-add-context")).toBeNull(); }); it("lists recent uploads newest first and attaches one as it was sent", () => { rememberUploads([{ path: "/tmp/d/old.pdf", name: "old.pdf", image: false }]); rememberUploads([{ path: "/tmp/d/0123456789abcdef.png", name: "shot.png", image: true }]); const onAttach = renderMenu(); fireEvent.mouseEnter(screen.getByTestId("chat-add-recent")); const rows = screen.getAllByTestId(/^chat-add-recent-/); expect(rows.map((r) => r.textContent)).toEqual(["shot.png", "old.pdf"]); fireEvent.click(rows[0]!); expect(onAttach).toHaveBeenCalledWith([ { path: "/tmp/d/0123456789abcdef.png", name: "shot.png", url: "/api/run/drop/0123456789abcdef.png", }, ]); }); });