import { describe, it, expect, vi } from "vitest"; import { render, screen } from "@testing-library/react"; import { TaskDetailModal } from "../TaskDetailModal"; import type { TaskDetail, Column, MergeResult, Task } from "@hai/core"; function makeTask(overrides: Partial = {}): TaskDetail { return { id: "HAI-099", description: "Test task", column: "in-progress" as Column, dependencies: [], prompt: "", steps: [], currentStep: 0, log: [], createdAt: "2026-01-01T00:00:00Z", updatedAt: "2026-01-01T00:00:00Z", ...overrides, } as TaskDetail; } const noop = vi.fn(); const noopMove = vi.fn(async () => ({}) as Task); const noopDelete = vi.fn(async () => ({}) as Task); const noopMerge = vi.fn(async () => ({ merged: false }) as MergeResult); const noopRetry = vi.fn(async () => ({}) as Task); describe("TaskDetailModal", () => { it("renders markdown-body without detail-prompt class when prompt exists", () => { const { container } = render( , ); const markdownDiv = container.querySelector(".markdown-body"); expect(markdownDiv).toBeTruthy(); expect(markdownDiv!.classList.contains("detail-prompt")).toBe(false); }); it("strips the leading heading from prompt and renders remaining markdown", () => { const { container } = render( , ); // The leading # heading should be stripped (modal has its own header) expect(container.querySelector(".markdown-body h1")).toBeNull(); expect(container.querySelector("strong")?.textContent).toBe("bold"); }); it("renders (no prompt) with detail-prompt class when prompt is absent", () => { const { container } = render( , ); const fallback = screen.getByText("(no prompt)"); expect(fallback).toBeTruthy(); expect(fallback.classList.contains("detail-prompt")).toBe(true); expect(fallback.classList.contains("markdown-body")).toBe(false); }); it("does not render a PROMPT.md heading", () => { render( , ); expect(screen.queryByText("PROMPT.md")).toBeNull(); }); it("renders Retry button when task status is 'failed'", () => { render( , ); expect(screen.getByText("Retry")).toBeTruthy(); }); it("does NOT render Retry button when task status is not 'failed'", () => { render( , ); expect(screen.queryByText("Retry")).toBeNull(); }); it("does NOT render Retry button when onRetryTask is not provided", () => { render( , ); expect(screen.queryByText("Retry")).toBeNull(); }); it("shows description exactly once for a task without title", () => { const { container } = render( , ); // The heading "HAI-099" should be stripped from the markdown const markdownBody = container.querySelector(".markdown-body"); expect(markdownBody?.innerHTML).not.toContain("HAI-099"); // Description appears in the markdown body expect(markdownBody?.textContent).toContain("Fix the login bug"); // The detail header shows the ID (not duplicated as markdown heading) expect(container.querySelector(".detail-id")?.textContent).toBe("HAI-099"); // The h2 title shows description, not the task ID const h2 = container.querySelector("h2.detail-title"); expect(h2?.textContent).toBe("Fix the login bug"); }); it("shows the title in

when task.title is set", () => { const { container } = render( , ); const h2 = container.querySelector("h2.detail-title"); expect(h2?.textContent).toBe("Implement dark mode"); }); it("always shows task.id in the detail-id badge regardless of title", () => { // With title const { container: withTitle } = render( , ); expect(withTitle.querySelector(".detail-id")?.textContent).toBe("HAI-099"); // Without title const { container: withoutTitle } = render( , ); expect(withoutTitle.querySelector(".detail-id")?.textContent).toBe("HAI-099"); }); it("activity list does not have nested scroll constraints", () => { const { container } = render( , ); const activityList = container.querySelector(".detail-activity-list"); expect(activityList).toBeTruthy(); const style = (activityList as HTMLElement).style; expect(style.overflowY).not.toBe("auto"); expect(style.maxHeight).toBe(""); }); });