Squash merge of feature/story-206 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
99 lines
3.4 KiB
TypeScript
99 lines
3.4 KiB
TypeScript
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(<InlineCodeWithRefs text="just some text" />);
|
|
expect(screen.getByText("just some text")).toBeInTheDocument();
|
|
expect(screen.queryByRole("button")).toBeNull();
|
|
});
|
|
|
|
it("renders a code reference as a clickable button", () => {
|
|
render(<InlineCodeWithRefs text="src/main.rs:42" />);
|
|
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(<InlineCodeWithRefs text="src/main.rs:42" />);
|
|
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(<InlineCodeWithRefs text="See src/lib.rs:10 for the impl" />);
|
|
// 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();
|
|
});
|
|
});
|