// @vitest-environment jsdom
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { queryKeys } from "@/lib/queryKeys";
import { act } from "react";
import { createRoot } from "react-dom/client";
import { afterEach, describe, expect, it, vi } from "vitest";
import { TaskAttachmentPanel, TextAttachmentPreview, readTextPreview, TEXT_PREVIEW_MAX_BYTES } from "./TaskAttachmentPanel";
import { isTextAttachment } from "@/lib/issue-attachments";
import { readTaskSidePanelState, taskPanelAttachmentTab, writeTaskSidePanelState } from "@/lib/task-side-panel-state";
vi.mock("@/components/MarkdownBody", () => ({ MarkdownBody: ({ children }: { children: string }) => {children} }));
(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true;
afterEach(() => { window.localStorage.clear(); vi.unstubAllGlobals(); });
describe("text attachment tabs", () => {
it("does not fetch missing or oversized attachments and offers download for oversized files", async () => {
const fetch = vi.fn(); vi.stubGlobal("fetch", fetch);
const client = new QueryClient({ defaultOptions: { queries: { staleTime: Infinity, retry: false } } });
client.setQueryData(queryKeys.issues.attachments("task"), [{ id: "large", originalFilename: "large.txt", contentType: "text/plain", byteSize: TEXT_PREVIEW_MAX_BYTES + 1, contentPath: "/api/attachments/large/content" }]);
const host = document.createElement("div"); const root = createRoot(host);
await act(async () => root.render());
expect(host.textContent).toContain("File no longer available");
await act(async () => root.render());
expect(host.textContent).toContain("too large");
expect(host.querySelector("a")?.textContent).toBe("Download file");
expect(fetch).not.toHaveBeenCalled();
await act(async () => root.unmount()); client.clear();
});
it("classifies text without treating PDF or images as text", () => {
for (const [contentType, originalFilename] of [["text/plain", "notes.txt"], ["application/octet-stream", "AGENTS.md"], ["application/json", "report.json"]]) expect(isTextAttachment({ contentType, originalFilename })).toBe(true);
expect(isTextAttachment({ contentType: "application/pdf", originalFilename: "report.pdf" })).toBe(false);
expect(isTextAttachment({ contentType: "image/png", originalFilename: "fake.txt" })).toBe(false);
});
it("retains attachment tabs when workspace browsing is disabled", () => {
const tab = taskPanelAttachmentTab("file-1", "AGENTS.md");
writeTaskSidePanelState("user", "company", "task", { state: { tabs: [tab], activeTabId: tab.id }, launcherOpen: false, userInteracted: true, autoPlanHandled: false, updatedAt: 1 });
expect(readTaskSidePanelState("user", "company", "task", false)?.state.tabs).toEqual([tab]);
expect(taskPanelAttachmentTab("file-1", "Renamed.md").id).toBe(tab.id);
});
it("switches rendered/raw Markdown and keeps a download action", async () => {
const host = document.createElement("div"); document.body.append(host);
const root = createRoot(host);
await act(async () => root.render());
expect(host.querySelector("[data-rendered]")).not.toBeNull();
const raw = host.querySelector('button[aria-label="Raw"]');
const rendered = host.querySelector('button[aria-label="Rendered"]');
expect(raw?.querySelector("svg")).not.toBeNull();
expect(rendered?.querySelector("svg")).not.toBeNull();
expect(raw?.textContent).toBe("");
await act(async () => raw!.click());
expect(host.querySelector("pre")?.textContent).toBe("# Charter");
expect(host.querySelector("a")?.getAttribute("href")).toContain("download=1");
await act(async () => rendered!.click());
expect(host.querySelector("[data-rendered]")).not.toBeNull();
await act(async () => root.unmount()); host.remove();
});
it("shows plain text literally without Markdown controls", async () => {
const host = document.createElement("div"); const root = createRoot(host);
await act(async () => root.render());
expect(host.querySelector("script")).toBeNull();
expect(host.querySelector("pre")?.textContent).toContain("