import { fireEvent, render, screen } from "@testing-library/react"; import { describe, expect, it, vi } from "vitest"; import { InlineCodeWithRefs, parseCodeRefs } from "./CodeRef"; // Mock the settingsApi so we don't make real HTTP calls in tests vi.mock("../api/settings", () => ({ settingsApi: { openFile: vi.fn(() => Promise.resolve({ success: true })), }, })); describe("parseCodeRefs (Story 193)", () => { it("returns a single text part for plain text with no code refs", () => { const parts = parseCodeRefs("Hello world, no code here"); expect(parts).toHaveLength(1); expect(parts[0]).toEqual({ type: "text", value: "Hello world, no code here", }); }); it("detects a simple code reference", () => { const parts = parseCodeRefs("src/main.rs:42"); expect(parts).toHaveLength(1); expect(parts[0]).toMatchObject({ type: "ref", path: "src/main.rs", line: 42, }); }); it("detects a code reference embedded in surrounding text", () => { const parts = parseCodeRefs("See src/lib.rs:100 for details"); expect(parts).toHaveLength(3); expect(parts[0]).toEqual({ type: "text", value: "See " }); expect(parts[1]).toMatchObject({ type: "ref", path: "src/lib.rs", line: 100, }); expect(parts[2]).toEqual({ type: "text", value: " for details" }); }); it("detects multiple code references", () => { const parts = parseCodeRefs("Check src/a.rs:1 and src/b.ts:200"); const refs = parts.filter((p) => p.type === "ref"); expect(refs).toHaveLength(2); expect(refs[0]).toMatchObject({ path: "src/a.rs", line: 1 }); expect(refs[1]).toMatchObject({ path: "src/b.ts", line: 200 }); }); it("does not match text without a file extension", () => { const parts = parseCodeRefs("something:42"); // "something" has no dot so it should not match expect(parts.every((p) => p.type === "text")).toBe(true); }); it("matches nested paths with multiple slashes", () => { const parts = parseCodeRefs("frontend/src/components/Chat.tsx:55"); expect(parts).toHaveLength(1); expect(parts[0]).toMatchObject({ type: "ref", path: "frontend/src/components/Chat.tsx", line: 55, }); }); }); describe("InlineCodeWithRefs component (Story 193)", () => { it("renders plain text without buttons", () => { render(); expect(screen.getByText("just some text")).toBeInTheDocument(); expect(screen.queryByRole("button")).toBeNull(); }); it("renders a code reference as a clickable button", () => { render(); const button = screen.getByRole("button", { name: /src\/main\.rs:42/ }); expect(button).toBeInTheDocument(); expect(button).toHaveAttribute("title", "Open src/main.rs:42 in editor"); }); it("calls settingsApi.openFile when a code reference is clicked", async () => { const { settingsApi } = await import("../api/settings"); render(); const button = screen.getByRole("button"); fireEvent.click(button); expect(settingsApi.openFile).toHaveBeenCalledWith("src/main.rs", 42); }); it("renders mixed text and code references correctly", () => { render(); // getByText normalizes text (trims whitespace), so "See " → "See" expect(screen.getByText("See")).toBeInTheDocument(); expect(screen.getByRole("button")).toBeInTheDocument(); expect(screen.getByText("for the impl")).toBeInTheDocument(); }); });