// @vitest-environment node import type { ComponentProps, ReactNode } from "react"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { describe, expect, it, vi } from "vitest"; import { renderToStaticMarkup } from "react-dom/server"; import { buildAgentMentionHref, buildIssueReferenceHref, buildProjectMentionHref, buildSkillMentionHref, buildUserMentionHref, } from "@paperclipai/shared"; import { ThemeProvider } from "../context/ThemeContext"; import { MarkdownBody } from "./MarkdownBody"; import { queryKeys } from "../lib/queryKeys"; const mockIssuesApi = vi.hoisted(() => ({ get: vi.fn(), })); vi.mock("@/lib/router", () => ({ Link: ({ children, to, ...props }: { children: ReactNode; to: string } & React.ComponentProps<"a">) => ( {children} ), })); vi.mock("../api/issues", () => ({ issuesApi: mockIssuesApi, })); function renderMarkdown( children: string, seededIssues: Array<{ identifier: string; status: string; title?: string }> = [], props: Partial> = {}, ) { const queryClient = new QueryClient({ defaultOptions: { queries: { retry: false, }, }, }); for (const issue of seededIssues) { queryClient.setQueryData(queryKeys.issues.detail(issue.identifier), { id: issue.identifier, identifier: issue.identifier, status: issue.status, title: issue.title, }); } return renderToStaticMarkup( {children} , ); } describe("MarkdownBody", () => { it("renders markdown images without a resolver", () => { const html = renderToStaticMarkup( {"![](/api/attachments/test/content)"} , ); expect(html).toContain(''); }); it("resolves relative image paths when a resolver is provided", () => { const html = renderToStaticMarkup( `/resolved/${src}`}> {"![Org chart](images/org-chart.png)"} , ); expect(html).toContain('src="/resolved/images/org-chart.png"'); expect(html).toContain('alt="Org chart"'); }); it("renders user, agent, project, and skill mentions as chips", () => { const html = renderToStaticMarkup( {`[@Taylor](${buildUserMentionHref("user-123")}) [@CodexCoder](${buildAgentMentionHref("agent-123", "code")}) [@Paperclip App](${buildProjectMentionHref("project-456", "#336699")}) [/release-changelog](${buildSkillMentionHref("skill-789", "release-changelog")})`} , ); expect(html).toContain('href="/company/settings/access"'); expect(html).toContain('data-mention-kind="user"'); expect(html).toContain('href="/agents/agent-123"'); expect(html).toContain('data-mention-kind="agent"'); expect(html).toContain("--paperclip-mention-icon-mask"); expect(html).toContain('href="/projects/project-456"'); expect(html).toContain('data-mention-kind="project"'); expect(html).toContain("--paperclip-mention-project-color:#336699"); expect(html).toContain('href="/skills/skill-789"'); expect(html).toContain('data-mention-kind="skill"'); }); it("sanitizes unsafe javascript markdown links", () => { const html = renderMarkdown("[click me](javascript:alert(document.cookie))"); expect(html).toContain('click me"); expect(html).not.toContain("javascript:"); }); it("renders raw HTML tags as escaped text", () => { const html = renderMarkdown( '\n\n

Plain text

', ); expect(html).not.toContain("