import type { ComponentType } from "react"; import { cleanup, fireEvent, render, screen } from "@testing-library/react"; import { afterEach, describe, expect, it, vi } from "vitest"; import type { EntityReference, EntityReferenceSearchGroup } from "@/lib/types/entity-reference"; import * as entityReferenceMenus from "./entity-reference-menu"; afterEach(cleanup); const unavailableSearchGroups: EntityReferenceSearchGroup[] = [ { source: "gitlab_issues", provider: "gitlab", kind: "issue", display_name: "GitLab", kind_label: "Issue", status: "unsupported_scope", results: [], }, { source: "linear_issues", provider: "linear", kind: "issue", display_name: "Linear", kind_label: "Issue", status: "not_configured", results: [], }, { source: "github_issues", provider: "github", kind: "issue", display_name: "GitHub", kind_label: "Issue", status: "timeout", results: [], }, ]; const kandevTaskGroup: EntityReferenceSearchGroup = { source: "kandev_tasks", provider: "kandev", kind: "task", display_name: "Kandev tasks", kind_label: "Task", status: "ok", results: [ { version: 1, ref: "mention:v1:kandev:task:workspace-1:task-1", provider: "kandev", kind: "task", id: "task-1", key: "", title: "Existing task", url: "/t/task-1", scope: "workspace-1", }, ], }; const githubIssueGroup: EntityReferenceSearchGroup = { source: "github_issues", provider: "github", kind: "issue", display_name: "GitHub", kind_label: "Issue", status: "ok", results: [ { version: 1, ref: "mention:v1:github:issue:octo%2Frepo:7", provider: "github", kind: "issue", id: "7", key: "#7", title: "External issue", url: "https://github.com/octo/repo/issues/7", scope: "octo/repo", }, ], }; type EntityReferenceMenuProps = { isOpen: boolean; clientRect: () => DOMRect; groups: EntityReferenceSearchGroup[]; query: string; selectedIndex: number; isSearching: boolean; error: null; onRetry: () => void; onSelect: (reference: EntityReference) => void; onClose: () => void; setSelectedIndex: (index: number) => void; }; const EntityReferenceMenu = entityReferenceMenus.EntityReferenceMenu as unknown as ComponentType; function renderEntityReferenceMenu( groups: EntityReferenceSearchGroup[], onSelect: (reference: EntityReference) => void, ) { render( new DOMRect(16, 240, 1, 20)} groups={groups} query="auth" selectedIndex={0} isSearching={false} error={null} onRetry={vi.fn()} onSelect={onSelect} onClose={vi.fn()} setSelectedIndex={vi.fn()} />, ); } describe("EntityReferenceMenu", () => { it("provides a menu distinct from @ context mentions", () => { expect(typeof (entityReferenceMenus as Record).EntityReferenceMenu).toBe( "function", ); }); it("renders descriptor-driven groups with a generic fallback and 44px touch rows", () => { const reference: EntityReference = { version: 1, ref: "mention:v1:plugin:acme:incident:scope:incident-9", provider: "plugin:acme:tracker", kind: "incident", id: "incident-9", key: "INC-9", title: "Authentication outage", url: "https://tracker.example.test/incidents/9", scope: "tracker.example.test/team-a", }; const groups: EntityReferenceSearchGroup[] = [ { source: "plugin:acme:incidents", provider: reference.provider, kind: reference.kind, display_name: "Acme tracker", kind_label: "Incident", status: "ok", results: [reference], }, ]; const onSelect = vi.fn(); renderEntityReferenceMenu(groups, onSelect); expect(screen.getByText("Acme tracker")).toBeTruthy(); expect(screen.getByTestId("entity-reference-menu")).toBeTruthy(); expect(screen.getByText("Incident")).toBeTruthy(); expect(screen.getByTestId("entity-reference-generic-icon")).toBeTruthy(); const row = screen.getByRole("option", { name: /#INC-9.*Authentication outage/ }); expect(row.className).toContain("min-h-11"); fireEvent.click(row); expect(onSelect).toHaveBeenCalledWith(reference); }); it("renders a plugin pull request without a provider-specific composer branch", () => { const reference: EntityReference = { version: 1, ref: "mention:v1:plugin%3Aexample%3Areviews:pull_request:workspace-1:pull-42", provider: "plugin:example:reviews", kind: "pull_request", id: "pull-42", key: "PR-42", title: "Fix authentication", url: "https://reviews.example.test/pull-requests/42", scope: "workspace-1", }; const onSelect = vi.fn(); renderEntityReferenceMenu( [ { source: "plugin:example:pull-requests", provider: reference.provider, kind: reference.kind, display_name: "Example reviews", kind_label: "Pull request", status: "ok", results: [reference], }, ], onSelect, ); const row = screen.getByRole("option", { name: /#PR-42.*Fix authentication/ }); expect(screen.getByText("Example reviews")).toBeTruthy(); expect(screen.getByText("Pull request")).toBeTruthy(); expect(row.querySelector("svg")?.getAttribute("class")).toContain("h-4"); expect(row.querySelector("svg")?.getAttribute("class")).toContain("w-4"); fireEvent.click(row); expect(onSelect).toHaveBeenCalledWith(reference); }); it("omits sources that are not searchable in the active workspace", () => { const visibleEntityReferenceGroups = (entityReferenceMenus as Record) .visibleEntityReferenceGroups; expect(visibleEntityReferenceGroups).toBeTypeOf("function"); if (typeof visibleEntityReferenceGroups !== "function") return; expect( (visibleEntityReferenceGroups as (value: EntityReferenceSearchGroup[]) => unknown)( unavailableSearchGroups, ), ).toEqual([unavailableSearchGroups[2]]); }); it("keeps Kandev tasks out of # work item search", () => { expect(entityReferenceMenus.visibleEntityReferenceGroups([kandevTaskGroup])).toEqual([]); const selectableEntityReferences = (entityReferenceMenus as Record) .selectableEntityReferences; expect(selectableEntityReferences).toBeTypeOf("function"); if (typeof selectableEntityReferences !== "function") return; expect( (selectableEntityReferences as (value: EntityReferenceSearchGroup[]) => unknown)([ kandevTaskGroup, githubIssueGroup, ]), ).toEqual(githubIssueGroup.results); }); });