mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-23 17:33:51 +02:00
Render eight artifact card types from real task records and share them with editable Storybook stories. Preserve document review and media/file actions, and load bounded CSV previews on request. Co-Authored-By: Paperclip <noreply@paperclip.ing>
276 lines
8.5 KiB
TypeScript
276 lines
8.5 KiB
TypeScript
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||
import { renderToStaticMarkup } from "react-dom/server";
|
||
import { describe, expect, it } from "vitest";
|
||
import type { IssueWorkProduct } from "@paperclipai/shared";
|
||
import {
|
||
IssueArtifactFile,
|
||
IssueWorkProductArtifactCard,
|
||
} from "./IssueArtifactCard";
|
||
|
||
import { DocumentCard } from "./RichArtifactCards";
|
||
|
||
function product(overrides: Partial<IssueWorkProduct> = {}): IssueWorkProduct {
|
||
return {
|
||
id: "wp-1",
|
||
companyId: "company-1",
|
||
projectId: null,
|
||
issueId: "issue-1",
|
||
executionWorkspaceId: null,
|
||
runtimeServiceId: null,
|
||
type: "pull_request",
|
||
provider: "github",
|
||
externalId: null,
|
||
title: "Actual artifact title",
|
||
url: "https://github.com/org/repo/pull/42",
|
||
status: "active",
|
||
reviewState: "none",
|
||
isPrimary: false,
|
||
healthStatus: "unknown",
|
||
summary: "Actual artifact summary",
|
||
metadata: null,
|
||
createdByRunId: null,
|
||
createdAt: new Date("2026-09-28"),
|
||
updatedAt: new Date("2026-09-28"),
|
||
...overrides,
|
||
};
|
||
}
|
||
function render(wp: IssueWorkProduct) {
|
||
return renderToStaticMarkup(
|
||
<QueryClientProvider client={new QueryClient()}>
|
||
<IssueWorkProductArtifactCard workProduct={wp} author="Actual agent" />
|
||
</QueryClientProvider>,
|
||
);
|
||
}
|
||
|
||
describe("production artifact cards", () => {
|
||
it("renders supplied PR metadata without synthesizing missing counts, checks or branch names", () => {
|
||
const sparse = render(product());
|
||
expect(sparse).toContain("Actual artifact title");
|
||
expect(sparse).toContain("Actual artifact summary");
|
||
expect(sparse).toContain("Actual agent");
|
||
expect(sparse).toContain("Checks not available");
|
||
expect(sparse).not.toContain("Checks passed");
|
||
expect(sparse).not.toContain("+0");
|
||
expect(sparse).not.toContain("#0");
|
||
expect(sparse).not.toContain("master");
|
||
const full = render(
|
||
product({
|
||
metadata: {
|
||
number: 42,
|
||
repo: "org/repo",
|
||
state: "draft",
|
||
additions: 0,
|
||
deletions: 8,
|
||
changedFiles: 2,
|
||
headRef: "feature",
|
||
baseRef: "main",
|
||
},
|
||
}),
|
||
);
|
||
for (const value of [
|
||
"#42",
|
||
"org/repo",
|
||
"Draft",
|
||
"+0",
|
||
"−8",
|
||
"feature",
|
||
"main",
|
||
])
|
||
expect(full).toContain(value);
|
||
});
|
||
it("preserves review and unhealthy states", () => {
|
||
expect(
|
||
render(product({ reviewState: "changes_requested", status: "merged" })),
|
||
).toContain("Changes requested");
|
||
expect(
|
||
render(product({ type: "preview_url", healthStatus: "unhealthy" })),
|
||
).toContain("Unhealthy");
|
||
expect(
|
||
render(product({ type: "preview_url", healthStatus: "unknown" })),
|
||
).not.toContain("Healthy");
|
||
});
|
||
it.each([
|
||
["ready_for_review", "Review"],
|
||
["changes_requested", "Changes requested"],
|
||
])(
|
||
"preserves PR status %s without a separate reviewState",
|
||
(status, label) => {
|
||
expect(render(product({ status, reviewState: "none" }))).toContain(label);
|
||
},
|
||
);
|
||
|
||
it("preserves browser-open actions for attachments and signed external file URLs", () => {
|
||
const signedUrl =
|
||
"https://files.example/report.pdf?signature=abc&expires=123";
|
||
const external = render(
|
||
product({
|
||
type: "artifact",
|
||
url: signedUrl,
|
||
metadata: { contentType: "application/pdf" },
|
||
}),
|
||
);
|
||
expect(external).toContain(
|
||
'href="https://files.example/report.pdf?signature=abc&expires=123"',
|
||
);
|
||
expect(external).not.toContain("?download=1");
|
||
expect(external).toContain("Open file");
|
||
expect(external).toContain("Download file");
|
||
|
||
const local = renderToStaticMarkup(
|
||
<QueryClientProvider client={new QueryClient()}>
|
||
<IssueArtifactFile
|
||
id="pdf-1"
|
||
title="Report"
|
||
summary=""
|
||
author=""
|
||
updatedAt="Today"
|
||
filename="report.pdf"
|
||
contentType="application/pdf"
|
||
contentPath="/api/attachments/pdf-1/content"
|
||
openPath="/api/attachments/pdf-1/content"
|
||
downloadPath="/api/attachments/pdf-1/content?download=1"
|
||
byteSize={100}
|
||
/>
|
||
</QueryClientProvider>,
|
||
);
|
||
expect(local).toContain(
|
||
'href="/api/attachments/pdf-1/content" target="_blank"',
|
||
);
|
||
expect(local).toContain(
|
||
'href="/api/attachments/pdf-1/content?download=1" download="report.pdf"',
|
||
);
|
||
});
|
||
|
||
it("selects commit, link, image, video and file renderers from real records", () => {
|
||
expect(
|
||
render(
|
||
product({
|
||
type: "commit",
|
||
metadata: { sha: "123456789abcdef", repo: "org/repo" },
|
||
}),
|
||
),
|
||
).toContain("12345678");
|
||
expect(render(product({ type: "preview_url" }))).toContain("Open link");
|
||
const attachment = {
|
||
contentPath: "/api/attachments/file-1/content",
|
||
byteSize: 25,
|
||
};
|
||
expect(
|
||
render(
|
||
product({
|
||
type: "artifact",
|
||
metadata: {
|
||
...attachment,
|
||
contentType: "image/png",
|
||
originalFilename: "actual.png",
|
||
},
|
||
}),
|
||
),
|
||
).toContain("View image");
|
||
const video = render(
|
||
product({
|
||
type: "artifact",
|
||
metadata: {
|
||
...attachment,
|
||
contentType: "video/mp4",
|
||
originalFilename: "actual.mp4",
|
||
},
|
||
}),
|
||
);
|
||
expect(video).toContain("<video");
|
||
expect(video).toContain("controls");
|
||
expect(video).not.toContain("autoplay");
|
||
const file = render(
|
||
product({
|
||
type: "artifact",
|
||
metadata: {
|
||
...attachment,
|
||
contentType: "application/pdf",
|
||
originalFilename: "actual.pdf",
|
||
},
|
||
}),
|
||
);
|
||
expect(file).toContain("Download file");
|
||
expect(file).toContain("25 B");
|
||
expect(file).toContain("?download=1");
|
||
});
|
||
it("shows parsed attachment rows and downloads the original CSV", () => {
|
||
const client = new QueryClient();
|
||
client.setQueryData(
|
||
["artifact-csv", "csv-1", "/api/attachments/csv-1/content", "Today"],
|
||
{ columns: ["Region"], rows: [["Actual region"]], truncated: false },
|
||
);
|
||
const html = renderToStaticMarkup(
|
||
<QueryClientProvider client={client}>
|
||
<IssueArtifactFile
|
||
id="csv-1"
|
||
title="Report"
|
||
summary=""
|
||
author=""
|
||
updatedAt="Today"
|
||
filename="actual.csv"
|
||
contentType="text/csv"
|
||
contentPath="/api/attachments/csv-1/content"
|
||
downloadPath="/api/attachments/csv-1/content?download=1"
|
||
byteSize={100}
|
||
/>
|
||
</QueryClientProvider>,
|
||
);
|
||
expect(html).toContain("Actual region");
|
||
expect(html).toContain("View data");
|
||
});
|
||
it("does not fetch remote Markdown images just to render a document preview", () => {
|
||
const html = renderToStaticMarkup(
|
||
<DocumentCard
|
||
title="Report"
|
||
summary=""
|
||
author=""
|
||
updatedAt=""
|
||
filename="report.md"
|
||
revision={1}
|
||
body="\n\n"
|
||
/>,
|
||
);
|
||
expect(html).not.toContain('src="https://tracker.example');
|
||
expect(html).toContain('href="https://tracker.example/image.png"');
|
||
expect(html).toContain('src="/api/attachments/image-1/content"');
|
||
});
|
||
it("does not automatically load remote thumbnail or poster metadata", () => {
|
||
const link = render(
|
||
product({
|
||
type: "preview_url",
|
||
metadata: { imageUrl: "https://tracker.example/pixel.png" },
|
||
}),
|
||
);
|
||
expect(link).not.toContain("<img");
|
||
expect(link).not.toContain("tracker.example");
|
||
const video = render(
|
||
product({
|
||
type: "artifact",
|
||
metadata: {
|
||
contentType: "video/mp4",
|
||
contentPath: "/api/attachments/file-1/content",
|
||
posterUrl: "https://tracker.example/poster.png",
|
||
},
|
||
}),
|
||
);
|
||
expect(video).not.toContain("poster=");
|
||
const local = render(
|
||
product({
|
||
type: "preview_url",
|
||
metadata: { imageUrl: "/api/attachments/thumbnail-1/content" },
|
||
}),
|
||
);
|
||
expect(local).toContain('src="/api/attachments/thumbnail-1/content"');
|
||
});
|
||
it("keeps branch and runtime actions and blocks unsafe artifact URLs", () => {
|
||
expect(render(product({ type: "branch" }))).toContain("Open on GitHub");
|
||
expect(
|
||
render(product({ type: "runtime_service", healthStatus: "unhealthy" })),
|
||
).toContain("Unhealthy");
|
||
expect(
|
||
render(product({ type: "preview_url", url: "javascript:alert(1)" })),
|
||
).not.toContain("javascript:");
|
||
});
|
||
});
|