test(KB-601): add test for pause/unpause with missing directory

This commit is contained in:
gsxdsm
2026-03-31 14:43:55 -07:00
parent fdc70cbd5c
commit b36c164261
21 changed files with 1090 additions and 24 deletions

View File

@@ -0,0 +1,50 @@
import { describe, it, expect } from "vitest";
import { render, screen } from "@testing-library/react";
import { MergeDetails } from "../MergeDetails";
const makeTask = (overrides: any = {}) => ({
id: "KB-001",
description: "Task",
column: "done",
dependencies: [],
steps: [],
currentStep: 0,
log: [],
createdAt: "2026-01-01T00:00:00.000Z",
updatedAt: "2026-01-01T00:00:00.000Z",
...overrides,
});
describe("MergeDetails", () => {
it("renders nothing when task is not done", () => {
const { container } = render(<MergeDetails task={makeTask({ column: "in-review", mergeDetails: { commitSha: "abc1234" } })} />);
expect(container.firstChild).toBeNull();
});
it("renders merge metadata for done task", () => {
render(
<MergeDetails
task={makeTask({
mergeDetails: {
commitSha: "abcdef123456",
filesChanged: 5,
insertions: 10,
deletions: 2,
mergedAt: "2026-01-01T01:00:00.000Z",
prNumber: 42,
mergeCommitMessage: "feat(KB-001): merge kb/kb-001",
mergeConfirmed: true,
},
})}
/>,
);
expect(screen.getByText("Merge Details")).toBeTruthy();
expect(screen.getByText("abcdef1")).toBeTruthy();
expect(screen.getByText("5")).toBeTruthy();
expect(screen.getByText("+10 / -2")).toBeTruthy();
expect(screen.getByText("#42")).toBeTruthy();
expect(screen.getByText("feat(KB-001): merge kb/kb-001")).toBeTruthy();
expect(screen.getByText("Merged successfully")).toBeTruthy();
});
});

View File

@@ -0,0 +1,67 @@
import { describe, it, expect, vi } from "vitest";
import { render, screen, fireEvent, waitFor } from "@testing-library/react";
import { TaskComments } from "../TaskComments";
vi.mock("../../api", () => ({
addTaskComment: vi.fn(),
updateTaskComment: vi.fn(),
deleteTaskComment: vi.fn(),
}));
import { addTaskComment, updateTaskComment, deleteTaskComment } from "../../api";
const makeTask = (overrides: any = {}) => ({
id: "KB-001",
description: "Task",
column: "todo",
dependencies: [],
steps: [],
currentStep: 0,
log: [],
createdAt: "2026-01-01T00:00:00.000Z",
updatedAt: "2026-01-01T00:00:00.000Z",
...overrides,
});
describe("TaskComments", () => {
it("renders empty state", () => {
render(<TaskComments task={makeTask()} addToast={vi.fn()} />);
expect(screen.getByText("No comments yet.")).toBeTruthy();
});
it("adds a comment", async () => {
const onTaskUpdated = vi.fn();
vi.mocked(addTaskComment).mockResolvedValue(makeTask({ comments: [{ id: "c1", text: "Hello", author: "user", createdAt: "2026-01-01T00:00:00.000Z" }] }));
render(<TaskComments task={makeTask()} addToast={vi.fn()} onTaskUpdated={onTaskUpdated} />);
fireEvent.change(screen.getByPlaceholderText("Add a comment"), { target: { value: "Hello" } });
fireEvent.click(screen.getByText("Add Comment"));
await waitFor(() => expect(addTaskComment).toHaveBeenCalledWith("KB-001", "Hello", "user"));
expect(onTaskUpdated).toHaveBeenCalled();
});
it("edits own comment", async () => {
const onTaskUpdated = vi.fn();
vi.mocked(updateTaskComment).mockResolvedValue(makeTask({ comments: [{ id: "c1", text: "Updated", author: "user", createdAt: "2026-01-01T00:00:00.000Z", updatedAt: "2026-01-01T00:01:00.000Z" }] }));
render(<TaskComments task={makeTask({ comments: [{ id: "c1", text: "Original", author: "user", createdAt: "2026-01-01T00:00:00.000Z" }] })} addToast={vi.fn()} onTaskUpdated={onTaskUpdated} />);
fireEvent.click(screen.getByText("Edit"));
fireEvent.change(screen.getByDisplayValue("Original"), { target: { value: "Updated" } });
fireEvent.click(screen.getByText("Save"));
await waitFor(() => expect(updateTaskComment).toHaveBeenCalledWith("KB-001", "c1", "Updated"));
expect(onTaskUpdated).toHaveBeenCalled();
});
it("deletes own comment", async () => {
const onTaskUpdated = vi.fn();
vi.mocked(deleteTaskComment).mockResolvedValue(makeTask({ comments: [] }));
render(<TaskComments task={makeTask({ comments: [{ id: "c1", text: "Original", author: "user", createdAt: "2026-01-01T00:00:00.000Z" }] })} addToast={vi.fn()} onTaskUpdated={onTaskUpdated} />);
fireEvent.click(screen.getByText("Delete"));
await waitFor(() => expect(deleteTaskComment).toHaveBeenCalledWith("KB-001", "c1"));
expect(onTaskUpdated).toHaveBeenCalled();
});
});

View File

@@ -115,6 +115,22 @@ describe("TaskDetailModal", () => {
expect(screen.queryByText("PROMPT.md")).toBeNull();
});
it("renders Comments tab", () => {
render(
<TaskDetailModal
task={makeTask()}
onClose={noop}
onMoveTask={noopMove}
onDeleteTask={noopDelete}
onMergeTask={noopMerge}
onOpenDetail={noopOpenDetail}
addToast={noop}
/>,
);
expect(screen.getByText("Comments")).toBeTruthy();
});
it("renders Retry button when task status is 'failed'", () => {
render(
<TaskDetailModal
@@ -1149,7 +1165,7 @@ describe("TaskDetailModal", () => {
);
const tabs = container.querySelectorAll(".detail-tab");
expect(tabs.length).toBe(5); // Definition, Activity, Agent Log, Steering, Model (Spec combined into Definition)
expect(tabs.length).toBe(6); // Definition, Activity, Agent Log, Steering, Comments, Model
// Tabs should use class-based styling, not inline styles
expect(tabs[0].classList.contains("detail-tab")).toBe(true);
expect(tabs[0].classList.contains("detail-tab-active")).toBe(true); // Definition is default active
@@ -1157,6 +1173,7 @@ describe("TaskDetailModal", () => {
expect(tabs[2].classList.contains("detail-tab-active")).toBe(false);
expect(tabs[3].classList.contains("detail-tab-active")).toBe(false);
expect(tabs[4].classList.contains("detail-tab-active")).toBe(false);
expect(tabs[5].classList.contains("detail-tab-active")).toBe(false);
// Verify no inline padding/fontSize (responsive CSS controls this)
expect((tabs[0] as HTMLElement).style.padding).toBe("");
expect((tabs[0] as HTMLElement).style.fontSize).toBe("");
@@ -1634,7 +1651,7 @@ describe("TaskDetailModal", () => {
});
});
it("shows all 5 tabs in correct order (Spec tab removed)", () => {
it("shows all 6 tabs in correct order with comments", () => {
const { container } = render(
<TaskDetailModal
task={makeTask()}
@@ -1648,12 +1665,13 @@ describe("TaskDetailModal", () => {
);
const tabs = container.querySelectorAll(".detail-tab");
expect(tabs.length).toBe(5);
expect(tabs.length).toBe(6);
expect(tabs[0].textContent).toBe("Definition");
expect(tabs[1].textContent).toBe("Activity");
expect(tabs[2].textContent).toBe("Agent Log");
expect(tabs[3].textContent).toBe("Steering");
expect(tabs[4].textContent).toBe("Model");
expect(tabs[4].textContent).toBe("Comments");
expect(tabs[5].textContent).toBe("Model");
});
it("shows empty state and Edit button when no prompt", () => {