// @vitest-environment jsdom
import { flushSync } from "react-dom";
import { createRoot, type Root } from "react-dom/client";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import type { IssueWorkProduct } from "@paperclipai/shared";
import { ThemeProvider } from "@/context/ThemeContext";
import { MemoryRouter } from "@/lib/router";
import { TaskChatProtocolCard } from "./TaskChatProtocolCard";
import type { TaskChatProtocolItem } from "./task-chat-model";
vi.mock("@/hooks/useArtifactsV1Enabled", () => ({ useArtifactsV1Enabled: () => ({ enabled: true, loaded: true }) }));
const ATTACHMENT_ID = "00000000-0000-4000-8000-000000000001";
const deckPdf = {
id: "work-product-1",
companyId: "company-1",
projectId: null,
issueId: "issue-1",
executionWorkspaceId: null,
runtimeServiceId: null,
type: "artifact",
provider: "paperclip",
externalId: null,
title: "Interface Craft revision — PDF",
url: null,
status: "ready_for_review",
reviewState: "none",
isPrimary: false,
healthStatus: "unknown",
summary: null,
metadata: {
attachmentId: ATTACHMENT_ID,
contentType: "application/pdf",
byteSize: 59085,
contentPath: `/api/attachments/${ATTACHMENT_ID}/content`,
openPath: `/api/attachments/${ATTACHMENT_ID}/content?inline=1`,
downloadPath: `/api/attachments/${ATTACHMENT_ID}/content?download=1`,
originalFilename: "acmehats-interface-craft.pdf",
},
createdByRunId: null,
createdAt: new Date("2026-09-28T11:07:00Z"),
updatedAt: new Date("2026-09-28T11:07:00Z"),
} as unknown as IssueWorkProduct;
describe("TaskChatProtocolCard resources with Artifacts V1 on", () => {
let container: HTMLDivElement;
let root: Root;
beforeEach(() => {
container = document.createElement("div");
document.body.appendChild(container);
root = createRoot(container);
});
afterEach(() => {
flushSync(() => root.unmount());
container.remove();
});
const render = (item: TaskChatProtocolItem) =>
flushSync(() =>
root.render(
,
),
);
it("renders a registered file as one row named after the file, with its size and state, opening where the card did", () => {
render({ id: "resource:deliverable:work-product-1", kind: "protocol", surface: "resource", resourceKind: "deliverable", title: deckPdf.title, subtitle: "file · ready for review", href: `/api/attachments/${ATTACHMENT_ID}/content?inline=1`, workProduct: deckPdf });
const row = container.querySelector('[data-testid="task-chat-resource-deliverable"]');
expect(row).not.toBeNull();
expect(row?.textContent).toContain("acmehats-interface-craft.pdf");
expect(row?.textContent).toContain("57.7 KB");
expect(row?.textContent).toContain("PDF");
const open = row?.querySelector('a[data-testid="task-chat-resource-deliverable-open"]');
expect(open?.getAttribute("href")).toBe(`/api/attachments/${ATTACHMENT_ID}/content?inline=1`);
expect(open?.getAttribute("target")).toBe("_blank");
expect(row?.querySelector('a[aria-label="Download acmehats-interface-craft.pdf"]')?.getAttribute("href")).toContain("download=1");
expect(container.querySelector('[data-testid="task-chat-rich-work-product-artifact"]')).toBeNull();
});
it("renders a pull request as a link row that opens in a new tab with its state", () => {
const pr = { ...deckPdf, id: "wp-pr", type: "pull_request", provider: "github", title: "Ship rich cards", url: "https://github.com/paperclipai/paperclip/pull/42", status: "merged", metadata: { number: 42 } } as unknown as IssueWorkProduct;
render({ id: "resource:deliverable:wp-pr", kind: "protocol", surface: "resource", resourceKind: "deliverable", title: pr.title, subtitle: "pull request · merged", href: pr.url, workProduct: pr });
const open = container.querySelector('a[data-testid="task-chat-resource-deliverable-open"]');
expect(open?.getAttribute("href")).toBe("https://github.com/paperclipai/paperclip/pull/42");
expect(open?.getAttribute("target")).toBe("_blank");
});
it("renders a task document as a row named after its key that stays in the app", () => {
render({
id: "resource:document:plan",
kind: "protocol",
surface: "resource",
resourceKind: "document",
title: "Launch plan",
subtitle: "Document · rev 1",
href: "#document-plan",
document: { id: "doc-plan", issueId: "issue-1", key: "plan", title: "Launch plan", format: "markdown", latestRevisionNumber: 1, createdByAgentId: null, updatedByAgentId: null, createdAt: new Date("2026-09-27T08:36:00Z"), updatedAt: new Date("2026-09-27T08:36:00Z") } as never,
});
const row = container.querySelector('[data-testid="task-chat-resource-document"]');
expect(row?.textContent).toContain("plan.md");
expect(row?.textContent).toContain("DOC");
const link = row?.querySelector('a[data-testid="task-chat-resource-document-open"]');
expect(link?.getAttribute("href")).toBe("#document-plan");
expect(link?.getAttribute("target")).toBeNull();
});
});