Files
fusion/packages/dashboard/app/components/TaskCard.test.tsx
Dustin Byrne c802108a02 refactor(HAI-116): rename kb to hai across all packages, CLI, and docs
- Rename npm packages from @kb/* to @hai/* and update all workspace references
- Rename CLI binary from kb to hai and config directory from .kb to .hai
- Update dashboard UI branding, titles, and references from kb to hai
- Update all test files, CI workflows, and documentation to reflect new naming
- Run comprehensive grep verification to ensure no stale kb references remain
2026-03-26 22:44:11 -04:00

160 lines
4.7 KiB
TypeScript

import { describe, it, expect, vi } from "vitest";
import { render, screen, fireEvent, waitFor } from "@testing-library/react";
import { TaskCard } from "./TaskCard";
import type { Task } from "@kb/core";
// Mock lucide-react to avoid SVG rendering issues in test env
vi.mock("lucide-react", () => ({
Link: () => null,
Clock: () => null,
}));
// Mock the api module
vi.mock("../api", () => ({
fetchTaskDetail: vi.fn(),
uploadAttachment: vi.fn(),
}));
import { uploadAttachment } from "../api";
function makeTask(overrides: Partial<Task> = {}): Task {
return {
id: "KB-001",
title: "Test task",
column: "in-progress",
status: undefined as any,
steps: [],
dependencies: [],
description: "",
...overrides,
} as Task;
}
const noop = () => {};
describe("TaskCard", () => {
it("renders the card ID text", () => {
render(<TaskCard task={makeTask()} onOpenDetail={noop} addToast={noop} />);
expect(screen.getByText("KB-001")).toBeDefined();
});
it("renders the status badge when task.status is set", () => {
render(
<TaskCard
task={makeTask({ status: "executing" })}
onOpenDetail={noop}
addToast={noop}
/>,
);
expect(screen.getByText("executing")).toBeDefined();
});
it("renders the status badge after the card ID in DOM order", () => {
const { container } = render(
<TaskCard
task={makeTask({ status: "executing" })}
onOpenDetail={noop}
addToast={noop}
/>,
);
const cardId = container.querySelector(".card-id")!;
const badge = container.querySelector(".card-status-badge")!;
expect(cardId).toBeDefined();
expect(badge).toBeDefined();
// Badge should be the next sibling of card-id
expect(cardId.nextElementSibling).toBe(badge);
});
it("does not render a status badge when task.status is falsy", () => {
const { container } = render(
<TaskCard task={makeTask({ status: undefined as any })} onOpenDetail={noop} addToast={noop} />,
);
expect(container.querySelector(".card-status-badge")).toBeNull();
});
it("shows drop indicator on file dragover and removes on dragleave", () => {
const { container } = render(
<TaskCard task={makeTask()} onOpenDetail={noop} addToast={noop} />,
);
const card = container.querySelector(".card")!;
// Simulate file dragover
fireEvent.dragOver(card, {
dataTransfer: { types: ["Files"], dropEffect: "none" },
});
expect(card.classList.contains("file-drop-target")).toBe(true);
// Simulate dragleave
fireEvent.dragLeave(card, {
dataTransfer: { types: ["Files"] },
});
expect(card.classList.contains("file-drop-target")).toBe(false);
});
it("does not show drop indicator for non-file drag", () => {
const { container } = render(
<TaskCard task={makeTask()} onOpenDetail={noop} addToast={noop} />,
);
const card = container.querySelector(".card")!;
// Simulate card dragover (not files)
fireEvent.dragOver(card, {
dataTransfer: { types: ["text/plain"], dropEffect: "none" },
});
expect(card.classList.contains("file-drop-target")).toBe(false);
});
it("calls uploadAttachment on file drop", async () => {
const mockUpload = vi.mocked(uploadAttachment);
mockUpload.mockResolvedValue({
filename: "abc-test.png",
originalName: "test.png",
mimeType: "image/png",
size: 1024,
createdAt: new Date().toISOString(),
});
const addToast = vi.fn();
const { container } = render(
<TaskCard task={makeTask()} onOpenDetail={noop} addToast={addToast} />,
);
const card = container.querySelector(".card")!;
const file = new File(["content"], "test.png", { type: "image/png" });
fireEvent.drop(card, {
dataTransfer: { types: ["Files"], files: [file] },
});
await waitFor(() => {
expect(mockUpload).toHaveBeenCalledWith("KB-001", file);
expect(addToast).toHaveBeenCalledWith(
expect.stringContaining("Attached test.png"),
"success",
);
});
});
it("shows error toast when upload fails", async () => {
const mockUpload = vi.mocked(uploadAttachment);
mockUpload.mockRejectedValue(new Error("Upload failed"));
const addToast = vi.fn();
const { container } = render(
<TaskCard task={makeTask()} onOpenDetail={noop} addToast={addToast} />,
);
const card = container.querySelector(".card")!;
const file = new File(["content"], "bad.png", { type: "image/png" });
fireEvent.drop(card, {
dataTransfer: { types: ["Files"], files: [file] },
});
await waitFor(() => {
expect(addToast).toHaveBeenCalledWith(
expect.stringContaining("Failed to attach bad.png"),
"error",
);
});
});
});