Files
fusion/packages/dashboard/app/components/__tests__/TaskDetailModal.test.tsx
Dustin Byrne f1c1f9e11a feat(HAI-043): fix title fallback to show description instead of duplicate ID
- Update TaskCard and TaskDetailModal to display description as fallback instead of repeating the task ID
- Remove unused hooks (useFlashOnIncrease), routes, assets, and dead CSS
- Clean up engine modules (executor, scheduler, triage, worktree-names) removing unused code
- Update and add tests for title fallback behavior in TaskDetailModal
- Fix pre-existing type error in test helper (types.ts)
2026-03-25 23:52:43 -04:00

218 lines
6.6 KiB
TypeScript

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> = {}): 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(
<TaskDetailModal
task={makeTask({ prompt: "# Hello\n\nSome **bold** text" })}
onClose={noop}
onMoveTask={noopMove}
onDeleteTask={noopDelete}
onMergeTask={noopMerge}
addToast={noop}
/>,
);
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(
<TaskDetailModal
task={makeTask({ prompt: "# Hello\n\nSome **bold** text" })}
onClose={noop}
onMoveTask={noopMove}
onDeleteTask={noopDelete}
onMergeTask={noopMerge}
addToast={noop}
/>,
);
// 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(
<TaskDetailModal
task={makeTask({ prompt: undefined })}
onClose={noop}
onMoveTask={noopMove}
onDeleteTask={noopDelete}
onMergeTask={noopMerge}
addToast={noop}
/>,
);
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(
<TaskDetailModal
task={makeTask({ prompt: "# Some prompt content" })}
onClose={noop}
onMoveTask={noopMove}
onDeleteTask={noopDelete}
onMergeTask={noopMerge}
addToast={noop}
/>,
);
expect(screen.queryByText("PROMPT.md")).toBeNull();
});
it("renders Retry button when task status is 'failed'", () => {
render(
<TaskDetailModal
task={makeTask({ status: "failed" })}
onClose={noop}
onMoveTask={noopMove}
onDeleteTask={noopDelete}
onMergeTask={noopMerge}
onRetryTask={noopRetry}
addToast={noop}
/>,
);
expect(screen.getByText("Retry")).toBeTruthy();
});
it("does NOT render Retry button when task status is not 'failed'", () => {
render(
<TaskDetailModal
task={makeTask({ status: "executing" })}
onClose={noop}
onMoveTask={noopMove}
onDeleteTask={noopDelete}
onMergeTask={noopMerge}
onRetryTask={noopRetry}
addToast={noop}
/>,
);
expect(screen.queryByText("Retry")).toBeNull();
});
it("does NOT render Retry button when onRetryTask is not provided", () => {
render(
<TaskDetailModal
task={makeTask({ status: "failed" })}
onClose={noop}
onMoveTask={noopMove}
onDeleteTask={noopDelete}
onMergeTask={noopMerge}
addToast={noop}
/>,
);
expect(screen.queryByText("Retry")).toBeNull();
});
it("shows description exactly once for a task without title", () => {
const { container } = render(
<TaskDetailModal
task={makeTask({
title: undefined,
description: "Fix the login bug",
prompt: "# HAI-099\n\nFix the login bug\n",
})}
onClose={noop}
onMoveTask={noopMove}
onDeleteTask={noopDelete}
onMergeTask={noopMerge}
addToast={noop}
/>,
);
// 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 <h2> when task.title is set", () => {
const { container } = render(
<TaskDetailModal
task={makeTask({
title: "Implement dark mode",
description: "Add dark mode toggle to the settings page",
})}
onClose={noop}
onMoveTask={noopMove}
onDeleteTask={noopDelete}
onMergeTask={noopMerge}
addToast={noop}
/>,
);
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(
<TaskDetailModal
task={makeTask({ title: "Some title" })}
onClose={noop}
onMoveTask={noopMove}
onDeleteTask={noopDelete}
onMergeTask={noopMerge}
addToast={noop}
/>,
);
expect(withTitle.querySelector(".detail-id")?.textContent).toBe("HAI-099");
// Without title
const { container: withoutTitle } = render(
<TaskDetailModal
task={makeTask({ title: undefined, description: "A description" })}
onClose={noop}
onMoveTask={noopMove}
onDeleteTask={noopDelete}
onMergeTask={noopMerge}
addToast={noop}
/>,
);
expect(withoutTitle.querySelector(".detail-id")?.textContent).toBe("HAI-099");
});
});