feat(KB-332): rename task prefix from KB to FN and branches from kb/ to fusion/
- Change default task prefix from KB to FN across all packages
- Rename branch naming pattern from kb/{id} to fusion/{id}
- Update all test assertions and fixtures to use new prefixes
- Add changeset for the breaking change
- Resolve merge conflicts in TaskCard.test.tsx touch gesture tests
This commit is contained in:
@@ -54,8 +54,8 @@ describe("FileBrowserModal", () => {
|
||||
mockUseWorkspaces.mockReturnValue({
|
||||
projectName: "kb",
|
||||
workspaces: [
|
||||
{ id: "KB-001", label: "KB-001", title: "Task One", worktree: "/repo/.worktrees/kb-001", kind: "task" },
|
||||
{ id: "KB-002", label: "KB-002", title: "Task Two", worktree: "/repo/.worktrees/kb-002", kind: "task" },
|
||||
{ id: "FN-001", label: "FN-001", title: "Task One", worktree: "/repo/.worktrees/kb-001", kind: "task" },
|
||||
{ id: "FN-002", label: "FN-002", title: "Task Two", worktree: "/repo/.worktrees/kb-002", kind: "task" },
|
||||
],
|
||||
loading: false,
|
||||
error: null,
|
||||
@@ -119,7 +119,7 @@ describe("FileBrowserModal", () => {
|
||||
await user.click(screen.getByRole("button", { name: /kb/i }));
|
||||
await user.click(screen.getByRole("button", { name: /KB-002 Task Two/i }));
|
||||
|
||||
expect(mockOnWorkspaceChange).toHaveBeenCalledWith("KB-002");
|
||||
expect(mockOnWorkspaceChange).toHaveBeenCalledWith("FN-002");
|
||||
});
|
||||
|
||||
it("shows back button in mobile editor view", async () => {
|
||||
|
||||
@@ -43,7 +43,7 @@ vi.mock("../api", () => ({
|
||||
|
||||
const mockTasks: Task[] = [
|
||||
{
|
||||
id: "KB-001",
|
||||
id: "FN-001",
|
||||
description: "Existing task 1",
|
||||
column: "todo",
|
||||
dependencies: [],
|
||||
@@ -414,7 +414,7 @@ describe("PlanningModeModal", () => {
|
||||
|
||||
it("creates task from summary", async () => {
|
||||
const createdTask: Task = {
|
||||
id: "KB-042",
|
||||
id: "FN-042",
|
||||
title: "Build authentication system",
|
||||
description: "Implement user auth with login and signup",
|
||||
column: "triage",
|
||||
@@ -472,7 +472,7 @@ describe("PlanningModeModal", () => {
|
||||
const onDeleteTask = vi.fn<(_: string) => Promise<Task>>().mockResolvedValue(mockTasks[0]);
|
||||
const onMergeTask = vi
|
||||
.fn<(_: string) => Promise<MergeResult>>()
|
||||
.mockResolvedValue({ merged: true, branch: "kb/kb-999", task: mockTasks[0], worktreeRemoved: true, branchDeleted: true });
|
||||
.mockResolvedValue({ merged: true, branch: "fusion/fn-999", task: mockTasks[0], worktreeRemoved: true, branchDeleted: true });
|
||||
|
||||
const { container } = render(
|
||||
<TaskDetailModal
|
||||
|
||||
@@ -38,7 +38,7 @@ describe("SubtaskBreakdownModal", () => {
|
||||
streamHandlers = handlers;
|
||||
return { close: vi.fn(), isConnected: () => true };
|
||||
});
|
||||
mockCreateTasksFromBreakdown.mockResolvedValue({ tasks: [{ id: "KB-101" }, { id: "KB-102" }] });
|
||||
mockCreateTasksFromBreakdown.mockResolvedValue({ tasks: [{ id: "FN-101" }, { id: "FN-102" }] });
|
||||
mockCancelSubtaskBreakdown.mockResolvedValue(undefined);
|
||||
vi.stubGlobal("confirm", vi.fn(() => true));
|
||||
});
|
||||
|
||||
@@ -26,7 +26,7 @@ import { uploadAttachment } from "../api";
|
||||
|
||||
function makeTask(overrides: Partial<Task> = {}): Task {
|
||||
return {
|
||||
id: "KB-001",
|
||||
id: "FN-001",
|
||||
title: "Test task",
|
||||
column: "in-progress",
|
||||
status: undefined as any,
|
||||
@@ -42,7 +42,7 @@ const noop = () => {};
|
||||
describe("TaskCard", () => {
|
||||
it("renders the card ID text", () => {
|
||||
render(<TaskCard task={makeTask()} onOpenDetail={noop} addToast={noop} />);
|
||||
expect(screen.getByText("KB-001")).toBeDefined();
|
||||
expect(screen.getByText("FN-001")).toBeDefined();
|
||||
});
|
||||
|
||||
it("renders the status badge when task.status is set", () => {
|
||||
@@ -133,7 +133,7 @@ describe("TaskCard", () => {
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockUpload).toHaveBeenCalledWith("KB-001", file);
|
||||
expect(mockUpload).toHaveBeenCalledWith("FN-001", file);
|
||||
expect(addToast).toHaveBeenCalledWith(
|
||||
expect.stringContaining("Attached test.png"),
|
||||
"success",
|
||||
|
||||
@@ -40,7 +40,7 @@ describe("WorkflowResultsTab", () => {
|
||||
];
|
||||
|
||||
it("renders list of workflow step results", () => {
|
||||
render(<WorkflowResultsTab taskId="KB-001" results={mockResults} />);
|
||||
render(<WorkflowResultsTab taskId="FN-001" results={mockResults} />);
|
||||
|
||||
expect(screen.getByTestId("workflow-results-list")).toBeInTheDocument();
|
||||
expect(screen.getByText("QA Check")).toBeInTheDocument();
|
||||
@@ -50,7 +50,7 @@ describe("WorkflowResultsTab", () => {
|
||||
});
|
||||
|
||||
it("renders correct status badges for each result", () => {
|
||||
render(<WorkflowResultsTab taskId="KB-001" results={mockResults} />);
|
||||
render(<WorkflowResultsTab taskId="FN-001" results={mockResults} />);
|
||||
|
||||
// Passed badge
|
||||
const passedBadge = screen.getByTestId("workflow-result-badge-WS-001");
|
||||
@@ -73,7 +73,7 @@ describe("WorkflowResultsTab", () => {
|
||||
});
|
||||
|
||||
it("shows output content for each result", () => {
|
||||
render(<WorkflowResultsTab taskId="KB-001" results={mockResults} />);
|
||||
render(<WorkflowResultsTab taskId="FN-001" results={mockResults} />);
|
||||
|
||||
expect(screen.getByTestId("workflow-result-output-WS-001")).toHaveTextContent(
|
||||
"All tests passed successfully."
|
||||
@@ -84,7 +84,7 @@ describe("WorkflowResultsTab", () => {
|
||||
});
|
||||
|
||||
it("handles results without output gracefully", () => {
|
||||
render(<WorkflowResultsTab taskId="KB-001" results={mockResults} />);
|
||||
render(<WorkflowResultsTab taskId="FN-001" results={mockResults} />);
|
||||
|
||||
// WS-003 and WS-004 have no output, so output elements should not be rendered
|
||||
expect(screen.queryByTestId("workflow-result-output-WS-003")).not.toBeInTheDocument();
|
||||
@@ -92,21 +92,21 @@ describe("WorkflowResultsTab", () => {
|
||||
});
|
||||
|
||||
it("shows empty state when no results", () => {
|
||||
render(<WorkflowResultsTab taskId="KB-001" results={[]} />);
|
||||
render(<WorkflowResultsTab taskId="FN-001" results={[]} />);
|
||||
|
||||
expect(screen.getByTestId("workflow-results-empty")).toBeInTheDocument();
|
||||
expect(screen.getByText("No workflow steps have run yet.")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("shows loading state when loading prop is true", () => {
|
||||
render(<WorkflowResultsTab taskId="KB-001" results={[]} loading={true} />);
|
||||
render(<WorkflowResultsTab taskId="FN-001" results={[]} loading={true} />);
|
||||
|
||||
expect(screen.getByTestId("workflow-results-loading")).toBeInTheDocument();
|
||||
expect(screen.getByText("Loading workflow results…")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("displays execution timestamps when available", () => {
|
||||
render(<WorkflowResultsTab taskId="KB-001" results={mockResults} />);
|
||||
render(<WorkflowResultsTab taskId="FN-001" results={mockResults} />);
|
||||
|
||||
// Check that timestamps are displayed for results that have them
|
||||
const timestamps = screen.getAllByText(/Started:/);
|
||||
@@ -114,7 +114,7 @@ describe("WorkflowResultsTab", () => {
|
||||
});
|
||||
|
||||
it("displays duration when start and end times are available", () => {
|
||||
render(<WorkflowResultsTab taskId="KB-001" results={mockResults} />);
|
||||
render(<WorkflowResultsTab taskId="FN-001" results={mockResults} />);
|
||||
|
||||
// The first result has a 2m 30s duration
|
||||
expect(screen.getByText("2m 30s")).toBeInTheDocument();
|
||||
@@ -130,7 +130,7 @@ describe("WorkflowResultsTab", () => {
|
||||
},
|
||||
];
|
||||
|
||||
render(<WorkflowResultsTab taskId="KB-001" results={resultsWithoutTimestamps} />);
|
||||
render(<WorkflowResultsTab taskId="FN-001" results={resultsWithoutTimestamps} />);
|
||||
|
||||
expect(screen.getByText("Simple Check")).toBeInTheDocument();
|
||||
// Should not crash without timestamps
|
||||
|
||||
@@ -18,8 +18,8 @@ describe("ActivityLogModal", () => {
|
||||
const mockOnOpenTaskDetail = vi.fn();
|
||||
|
||||
const mockTasks = [
|
||||
{ id: "KB-001", title: "Test Task 1", column: "todo" as const },
|
||||
{ id: "KB-002", title: "Test Task 2", column: "in-progress" as const },
|
||||
{ id: "FN-001", title: "Test Task 1", column: "todo" as const },
|
||||
{ id: "FN-002", title: "Test Task 2", column: "in-progress" as const },
|
||||
];
|
||||
|
||||
const mockActivityEntries: ActivityLogEntry[] = [
|
||||
@@ -27,7 +27,7 @@ describe("ActivityLogModal", () => {
|
||||
id: "1",
|
||||
timestamp: new Date().toISOString(),
|
||||
type: "task:created",
|
||||
taskId: "KB-001",
|
||||
taskId: "FN-001",
|
||||
taskTitle: "Test Task 1",
|
||||
details: "Task KB-001 created",
|
||||
},
|
||||
@@ -35,7 +35,7 @@ describe("ActivityLogModal", () => {
|
||||
id: "2",
|
||||
timestamp: new Date(Date.now() - 60000).toISOString(),
|
||||
type: "task:moved",
|
||||
taskId: "KB-001",
|
||||
taskId: "FN-001",
|
||||
taskTitle: "Test Task 1",
|
||||
details: "Task KB-001 moved: todo → in-progress",
|
||||
metadata: { from: "todo", to: "in-progress" },
|
||||
@@ -44,7 +44,7 @@ describe("ActivityLogModal", () => {
|
||||
id: "3",
|
||||
timestamp: new Date(Date.now() - 120000).toISOString(),
|
||||
type: "task:failed",
|
||||
taskId: "KB-002",
|
||||
taskId: "FN-002",
|
||||
taskTitle: "Test Task 2",
|
||||
details: "Task KB-002 failed: Something went wrong",
|
||||
metadata: { error: "Something went wrong" },
|
||||
|
||||
@@ -36,7 +36,7 @@ describe("AgentListModal", () => {
|
||||
name: "Test Agent 2",
|
||||
role: "triage" as AgentCapability,
|
||||
state: "active" as AgentState,
|
||||
taskId: "KB-001",
|
||||
taskId: "FN-001",
|
||||
lastHeartbeatAt: new Date().toISOString(),
|
||||
createdAt: new Date(Date.now() - 86400000).toISOString(),
|
||||
updatedAt: new Date().toISOString(),
|
||||
@@ -209,7 +209,7 @@ describe("AgentListModal", () => {
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText("KB-001")).toBeTruthy();
|
||||
expect(screen.getByText("FN-001")).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ import type { AgentLogEntry } from "@fusion/core";
|
||||
function makeEntry(overrides: Partial<AgentLogEntry> = {}): AgentLogEntry {
|
||||
return {
|
||||
timestamp: "2026-01-01T00:00:00Z",
|
||||
taskId: "KB-001",
|
||||
taskId: "FN-001",
|
||||
text: "Hello world",
|
||||
type: "text",
|
||||
...overrides,
|
||||
|
||||
@@ -109,17 +109,17 @@ describe("Board", () => {
|
||||
|
||||
it("filters tasks by ID when search query is provided", () => {
|
||||
const tasks: Task[] = [
|
||||
createTask({ id: "KB-001", description: "First task", column: "todo" }),
|
||||
createTask({ id: "KB-002", description: "Second task", column: "todo" }),
|
||||
createTask({ id: "KB-003", description: "Third task", column: "in-progress" }),
|
||||
createTask({ id: "FN-001", description: "First task", column: "todo" }),
|
||||
createTask({ id: "FN-002", description: "Second task", column: "todo" }),
|
||||
createTask({ id: "FN-003", description: "Third task", column: "in-progress" }),
|
||||
];
|
||||
|
||||
renderBoard({ tasks, searchQuery: "KB-002" });
|
||||
renderBoard({ tasks, searchQuery: "FN-002" });
|
||||
|
||||
const todoColumn = screen.getByTestId("column-todo");
|
||||
const todoTasks = JSON.parse(todoColumn.getAttribute("data-tasks") || "[]");
|
||||
expect(todoTasks).toHaveLength(1);
|
||||
expect(todoTasks[0].id).toBe("KB-002");
|
||||
expect(todoTasks[0].id).toBe("FN-002");
|
||||
|
||||
const inProgressColumn = screen.getByTestId("column-in-progress");
|
||||
const inProgressTasks = JSON.parse(inProgressColumn.getAttribute("data-tasks") || "[]");
|
||||
@@ -128,9 +128,9 @@ describe("Board", () => {
|
||||
|
||||
it("filters tasks by title when search query is provided", () => {
|
||||
const tasks: Task[] = [
|
||||
createTask({ id: "KB-001", title: "Fix login bug", description: "First task", column: "todo" }),
|
||||
createTask({ id: "KB-002", title: "Add dashboard feature", description: "Second task", column: "todo" }),
|
||||
createTask({ id: "KB-003", title: "Update documentation", description: "Third task", column: "todo" }),
|
||||
createTask({ id: "FN-001", title: "Fix login bug", description: "First task", column: "todo" }),
|
||||
createTask({ id: "FN-002", title: "Add dashboard feature", description: "Second task", column: "todo" }),
|
||||
createTask({ id: "FN-003", title: "Update documentation", description: "Third task", column: "todo" }),
|
||||
];
|
||||
|
||||
renderBoard({ tasks, searchQuery: "dashboard" });
|
||||
@@ -138,14 +138,14 @@ describe("Board", () => {
|
||||
const todoColumn = screen.getByTestId("column-todo");
|
||||
const todoTasks = JSON.parse(todoColumn.getAttribute("data-tasks") || "[]");
|
||||
expect(todoTasks).toHaveLength(1);
|
||||
expect(todoTasks[0].id).toBe("KB-002");
|
||||
expect(todoTasks[0].id).toBe("FN-002");
|
||||
});
|
||||
|
||||
it("filters tasks by description when search query is provided", () => {
|
||||
const tasks: Task[] = [
|
||||
createTask({ id: "KB-001", description: "Implement user authentication", column: "todo" }),
|
||||
createTask({ id: "KB-002", description: "Fix database connection issue", column: "todo" }),
|
||||
createTask({ id: "KB-003", description: "Add caching layer", column: "todo" }),
|
||||
createTask({ id: "FN-001", description: "Implement user authentication", column: "todo" }),
|
||||
createTask({ id: "FN-002", description: "Fix database connection issue", column: "todo" }),
|
||||
createTask({ id: "FN-003", description: "Add caching layer", column: "todo" }),
|
||||
];
|
||||
|
||||
renderBoard({ tasks, searchQuery: "database" });
|
||||
@@ -153,13 +153,13 @@ describe("Board", () => {
|
||||
const todoColumn = screen.getByTestId("column-todo");
|
||||
const todoTasks = JSON.parse(todoColumn.getAttribute("data-tasks") || "[]");
|
||||
expect(todoTasks).toHaveLength(1);
|
||||
expect(todoTasks[0].id).toBe("KB-002");
|
||||
expect(todoTasks[0].id).toBe("FN-002");
|
||||
});
|
||||
|
||||
it("search is case-insensitive", () => {
|
||||
const tasks: Task[] = [
|
||||
createTask({ id: "KB-001", title: "Fix Login Bug", description: "First task", column: "todo" }),
|
||||
createTask({ id: "KB-002", title: "Add Dashboard Feature", description: "Second task", column: "todo" }),
|
||||
createTask({ id: "FN-001", title: "Fix Login Bug", description: "First task", column: "todo" }),
|
||||
createTask({ id: "FN-002", title: "Add Dashboard Feature", description: "Second task", column: "todo" }),
|
||||
];
|
||||
|
||||
renderBoard({ tasks, searchQuery: "login" });
|
||||
@@ -167,12 +167,12 @@ describe("Board", () => {
|
||||
const todoColumn = screen.getByTestId("column-todo");
|
||||
const todoTasks = JSON.parse(todoColumn.getAttribute("data-tasks") || "[]");
|
||||
expect(todoTasks).toHaveLength(1);
|
||||
expect(todoTasks[0].id).toBe("KB-001");
|
||||
expect(todoTasks[0].id).toBe("FN-001");
|
||||
});
|
||||
|
||||
it("search is case-insensitive for lowercase query matching uppercase content", () => {
|
||||
const tasks: Task[] = [
|
||||
createTask({ id: "KB-UPPER", title: "UPPERCASE TITLE", description: "DESC", column: "todo" }),
|
||||
createTask({ id: "FN-UPPER", title: "UPPERCASE TITLE", description: "DESC", column: "todo" }),
|
||||
];
|
||||
|
||||
renderBoard({ tasks, searchQuery: "upper" });
|
||||
@@ -180,14 +180,14 @@ describe("Board", () => {
|
||||
const todoColumn = screen.getByTestId("column-todo");
|
||||
const todoTasks = JSON.parse(todoColumn.getAttribute("data-tasks") || "[]");
|
||||
expect(todoTasks).toHaveLength(1);
|
||||
expect(todoTasks[0].id).toBe("KB-UPPER");
|
||||
expect(todoTasks[0].id).toBe("FN-UPPER");
|
||||
});
|
||||
|
||||
it("shows all tasks when search query is empty", () => {
|
||||
const tasks: Task[] = [
|
||||
createTask({ id: "KB-001", description: "First task", column: "todo" }),
|
||||
createTask({ id: "KB-002", description: "Second task", column: "todo" }),
|
||||
createTask({ id: "KB-003", description: "Third task", column: "in-progress" }),
|
||||
createTask({ id: "FN-001", description: "First task", column: "todo" }),
|
||||
createTask({ id: "FN-002", description: "Second task", column: "todo" }),
|
||||
createTask({ id: "FN-003", description: "Third task", column: "in-progress" }),
|
||||
];
|
||||
|
||||
renderBoard({ tasks, searchQuery: "" });
|
||||
@@ -203,8 +203,8 @@ describe("Board", () => {
|
||||
|
||||
it("shows no tasks when search query matches nothing", () => {
|
||||
const tasks: Task[] = [
|
||||
createTask({ id: "KB-001", description: "First task", column: "todo" }),
|
||||
createTask({ id: "KB-002", description: "Second task", column: "todo" }),
|
||||
createTask({ id: "FN-001", description: "First task", column: "todo" }),
|
||||
createTask({ id: "FN-002", description: "Second task", column: "todo" }),
|
||||
];
|
||||
|
||||
renderBoard({ tasks, searchQuery: "nonexistent" });
|
||||
@@ -216,8 +216,8 @@ describe("Board", () => {
|
||||
|
||||
it("keeps unaffected columns stable when archived collapse toggles", () => {
|
||||
const tasks: Task[] = [
|
||||
createTask({ id: "KB-001", description: "Todo task", column: "todo" }),
|
||||
createTask({ id: "KB-002", description: "Archived task", column: "archived" }),
|
||||
createTask({ id: "FN-001", description: "Todo task", column: "todo" }),
|
||||
createTask({ id: "FN-002", description: "Archived task", column: "archived" }),
|
||||
];
|
||||
|
||||
renderBoard({ tasks });
|
||||
@@ -233,8 +233,8 @@ describe("Board", () => {
|
||||
|
||||
it("only re-renders the affected column when a task updates", () => {
|
||||
const tasks: Task[] = [
|
||||
createTask({ id: "KB-001", description: "Todo task", column: "todo", title: "Original" }),
|
||||
createTask({ id: "KB-002", description: "Done task", column: "done", title: "Done" }),
|
||||
createTask({ id: "FN-001", description: "Todo task", column: "todo", title: "Original" }),
|
||||
createTask({ id: "FN-002", description: "Done task", column: "done", title: "Done" }),
|
||||
];
|
||||
|
||||
const { rerender } = renderBoard({ tasks });
|
||||
@@ -262,21 +262,21 @@ describe("Board", () => {
|
||||
it("filtered tasks are sorted correctly (columnMovedAt, createdAt)", () => {
|
||||
const tasks: Task[] = [
|
||||
createTask({
|
||||
id: "KB-001",
|
||||
id: "FN-001",
|
||||
description: "Old task with move time",
|
||||
column: "todo",
|
||||
columnMovedAt: "2024-01-01T10:00:00.000Z",
|
||||
createdAt: "2024-01-01T08:00:00.000Z",
|
||||
}),
|
||||
createTask({
|
||||
id: "KB-002",
|
||||
id: "FN-002",
|
||||
description: "Newer task with move time",
|
||||
column: "todo",
|
||||
columnMovedAt: "2024-01-01T12:00:00.000Z",
|
||||
createdAt: "2024-01-01T08:00:00.000Z",
|
||||
}),
|
||||
createTask({
|
||||
id: "KB-003",
|
||||
id: "FN-003",
|
||||
description: "Legacy task no move time",
|
||||
column: "todo",
|
||||
createdAt: "2024-01-01T09:00:00.000Z",
|
||||
@@ -294,16 +294,16 @@ describe("Board", () => {
|
||||
// Tasks with columnMovedAt should come first, sorted by columnMovedAt descending (newest first)
|
||||
// So KB-002 (12:00) should be first, KB-001 (10:00) second
|
||||
// Legacy tasks (no columnMovedAt) come last, sorted by createdAt ascending
|
||||
expect(todoTasks[0].id).toBe("KB-002");
|
||||
expect(todoTasks[1].id).toBe("KB-001");
|
||||
expect(todoTasks[2].id).toBe("KB-003");
|
||||
expect(todoTasks[0].id).toBe("FN-002");
|
||||
expect(todoTasks[1].id).toBe("FN-001");
|
||||
expect(todoTasks[2].id).toBe("FN-003");
|
||||
});
|
||||
|
||||
it("matches tasks across multiple fields simultaneously", () => {
|
||||
const tasks: Task[] = [
|
||||
createTask({ id: "SEARCH-123", title: "Searchable title", description: "Normal description", column: "todo" }),
|
||||
createTask({ id: "KB-999", title: "Other task", description: "This has searchable content", column: "todo" }),
|
||||
createTask({ id: "KB-888", title: "Unrelated", description: "No match here", column: "todo" }),
|
||||
createTask({ id: "FN-888", title: "Unrelated", description: "No match here", column: "todo" }),
|
||||
];
|
||||
|
||||
renderBoard({ tasks, searchQuery: "search" });
|
||||
@@ -318,7 +318,7 @@ describe("Board", () => {
|
||||
|
||||
it("trims whitespace from search query", () => {
|
||||
const tasks: Task[] = [
|
||||
createTask({ id: "KB-001", description: "First task", column: "todo" }),
|
||||
createTask({ id: "FN-001", description: "First task", column: "todo" }),
|
||||
];
|
||||
|
||||
renderBoard({ tasks, searchQuery: " " });
|
||||
|
||||
@@ -55,7 +55,7 @@ const defaultProps = {
|
||||
|
||||
describe("Column count-flash", () => {
|
||||
it("does not apply count-flash class on initial render", () => {
|
||||
const tasks = [makeTask("KB-001")];
|
||||
const tasks = [makeTask("FN-001")];
|
||||
render(<Column {...defaultProps} tasks={tasks} />);
|
||||
|
||||
const badge = screen.getByText("1");
|
||||
@@ -64,10 +64,10 @@ describe("Column count-flash", () => {
|
||||
});
|
||||
|
||||
it("applies count-flash class when task count increases", () => {
|
||||
const tasks = [makeTask("KB-001")];
|
||||
const tasks = [makeTask("FN-001")];
|
||||
const { rerender } = render(<Column {...defaultProps} tasks={tasks} />);
|
||||
|
||||
const moreTasks = [makeTask("KB-001"), makeTask("KB-002")];
|
||||
const moreTasks = [makeTask("FN-001"), makeTask("FN-002")];
|
||||
rerender(<Column {...defaultProps} tasks={moreTasks} />);
|
||||
|
||||
const badge = screen.getByText("2");
|
||||
@@ -75,10 +75,10 @@ describe("Column count-flash", () => {
|
||||
});
|
||||
|
||||
it("does not apply count-flash class when task count decreases", () => {
|
||||
const tasks = [makeTask("KB-001"), makeTask("KB-002")];
|
||||
const tasks = [makeTask("FN-001"), makeTask("FN-002")];
|
||||
const { rerender } = render(<Column {...defaultProps} tasks={tasks} />);
|
||||
|
||||
const fewerTasks = [makeTask("KB-001")];
|
||||
const fewerTasks = [makeTask("FN-001")];
|
||||
rerender(<Column {...defaultProps} tasks={fewerTasks} />);
|
||||
|
||||
const badge = screen.getByText("1");
|
||||
@@ -88,7 +88,7 @@ describe("Column count-flash", () => {
|
||||
|
||||
describe("Column memoization", () => {
|
||||
it("does not re-render task cards when rerendered with the same task references", () => {
|
||||
const tasks = [makeTask("KB-001")];
|
||||
const tasks = [makeTask("FN-001")];
|
||||
const props = { ...defaultProps, tasks };
|
||||
|
||||
const { rerender } = render(<Column {...props} />);
|
||||
@@ -182,19 +182,19 @@ describe("Column pagination", () => {
|
||||
|
||||
describe("Column QuickEntryBox", () => {
|
||||
it("renders QuickEntryBox in triage column when onQuickCreate is provided", () => {
|
||||
const tasks = [makeTask("KB-001")];
|
||||
const tasks = [makeTask("FN-001")];
|
||||
render(<Column {...defaultProps} tasks={tasks} onQuickCreate={vi.fn()} />);
|
||||
expect(screen.getByTestId("quick-entry-box")).toBeTruthy();
|
||||
});
|
||||
|
||||
it("does not render QuickEntryBox in triage column when onQuickCreate is not provided", () => {
|
||||
const tasks = [makeTask("KB-001")];
|
||||
const tasks = [makeTask("FN-001")];
|
||||
render(<Column {...defaultProps} tasks={tasks} />);
|
||||
expect(screen.queryByTestId("quick-entry-box")).toBeNull();
|
||||
});
|
||||
|
||||
it("does not render QuickEntryBox in non-triage columns", () => {
|
||||
const tasks = [makeTask("KB-001")];
|
||||
const tasks = [makeTask("FN-001")];
|
||||
render(<Column {...defaultProps} tasks={tasks} column="todo" onQuickCreate={vi.fn()} />);
|
||||
expect(screen.queryByTestId("quick-entry-box")).toBeNull();
|
||||
});
|
||||
@@ -204,13 +204,13 @@ describe("Column same-column drop", () => {
|
||||
it("does not call onMoveTask when dropping task into its current column", () => {
|
||||
const onMoveTask = vi.fn().mockResolvedValue({} as Task);
|
||||
const addToast = vi.fn();
|
||||
const tasks = [{ ...makeTask("KB-001"), column: "todo" as ColumnType }];
|
||||
const tasks = [{ ...makeTask("FN-001"), column: "todo" as ColumnType }];
|
||||
|
||||
render(<Column {...defaultProps} column="todo" tasks={tasks} onMoveTask={onMoveTask} addToast={addToast} />);
|
||||
|
||||
const columnEl = screen.getByText("1").closest(".column") as HTMLElement;
|
||||
const dataTransfer = {
|
||||
getData: vi.fn().mockReturnValue("KB-001"),
|
||||
getData: vi.fn().mockReturnValue("FN-001"),
|
||||
dropEffect: "move",
|
||||
};
|
||||
|
||||
@@ -222,13 +222,13 @@ describe("Column same-column drop", () => {
|
||||
|
||||
it("removes drag-over styling after drop even on same column", () => {
|
||||
const onMoveTask = vi.fn().mockResolvedValue({} as Task);
|
||||
const tasks = [{ ...makeTask("KB-001"), column: "todo" as ColumnType }];
|
||||
const tasks = [{ ...makeTask("FN-001"), column: "todo" as ColumnType }];
|
||||
|
||||
render(<Column {...defaultProps} column="todo" tasks={tasks} onMoveTask={onMoveTask} />);
|
||||
|
||||
const columnEl = screen.getByText("1").closest(".column") as HTMLElement;
|
||||
const dataTransfer = {
|
||||
getData: vi.fn().mockReturnValue("KB-001"),
|
||||
getData: vi.fn().mockReturnValue("FN-001"),
|
||||
dropEffect: "move",
|
||||
};
|
||||
|
||||
@@ -253,12 +253,12 @@ describe("Column same-column drop", () => {
|
||||
|
||||
const columnEl = screen.getByText("0").closest(".column") as HTMLElement;
|
||||
const dataTransfer = {
|
||||
getData: vi.fn().mockReturnValue("KB-001"),
|
||||
getData: vi.fn().mockReturnValue("FN-001"),
|
||||
dropEffect: "move",
|
||||
};
|
||||
|
||||
fireEvent.drop(columnEl, { dataTransfer });
|
||||
|
||||
expect(onMoveTask).toHaveBeenCalledWith("KB-001", "in-review");
|
||||
expect(onMoveTask).toHaveBeenCalledWith("FN-001", "in-review");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -25,7 +25,7 @@ vi.mock("../../api", async (importOriginal) => {
|
||||
});
|
||||
|
||||
const mockTask: Task = {
|
||||
id: "KB-001",
|
||||
id: "FN-001",
|
||||
title: "Test Issue",
|
||||
description: "Test body\n\nSource: https://github.com/owner/repo/issues/1",
|
||||
column: "triage",
|
||||
@@ -38,7 +38,7 @@ const mockTask: Task = {
|
||||
};
|
||||
|
||||
const mockPRTask: Task = {
|
||||
id: "KB-002",
|
||||
id: "FN-002",
|
||||
title: "Review PR #1: Test PR",
|
||||
description: "Review and address any issues in this pull request.\n\nPR: https://github.com/owner/repo/pull/1\nBranch: feature → main\n\nPR body",
|
||||
column: "triage",
|
||||
|
||||
@@ -69,7 +69,7 @@ const mockAddToast = vi.fn();
|
||||
|
||||
const mockTasks: Task[] = [
|
||||
{
|
||||
id: "KB-001",
|
||||
id: "FN-001",
|
||||
description: "Test task 1",
|
||||
column: "in-progress",
|
||||
dependencies: [],
|
||||
@@ -81,7 +81,7 @@ const mockTasks: Task[] = [
|
||||
updatedAt: new Date().toISOString(),
|
||||
},
|
||||
{
|
||||
id: "KB-002",
|
||||
id: "FN-002",
|
||||
description: "Test task 2",
|
||||
column: "todo",
|
||||
dependencies: [],
|
||||
@@ -126,10 +126,10 @@ describe("GitManagerModal", () => {
|
||||
(fetchGitWorktrees as any).mockResolvedValue([
|
||||
{
|
||||
path: "/worktrees/kb-001",
|
||||
branch: "kb/kb-001",
|
||||
branch: "fusion/fn-001",
|
||||
isMain: false,
|
||||
isBare: false,
|
||||
taskId: "KB-001",
|
||||
taskId: "FN-001",
|
||||
},
|
||||
{ path: "/repo", branch: "main", isMain: true, isBare: false },
|
||||
]);
|
||||
@@ -634,7 +634,7 @@ describe("GitManagerModal", () => {
|
||||
fireEvent.click(screen.getByRole("tab", { name: /worktrees/i }));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText("KB-001")).toBeInTheDocument();
|
||||
expect(screen.getByText("FN-001")).toBeInTheDocument();
|
||||
expect(screen.getByText("2 total")).toBeInTheDocument();
|
||||
expect(screen.getByText("1 in use")).toBeInTheDocument();
|
||||
});
|
||||
@@ -647,7 +647,7 @@ describe("GitManagerModal", () => {
|
||||
fireEvent.click(screen.getByRole("tab", { name: /worktrees/i }));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText("kb/kb-001")).toBeInTheDocument();
|
||||
expect(screen.getByText("fusion/fn-001")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -49,7 +49,7 @@ const MOCK_MODELS: ModelInfo[] = [
|
||||
|
||||
function createMockTask(overrides: Partial<Task> = {}): Task {
|
||||
return {
|
||||
id: "KB-001",
|
||||
id: "FN-001",
|
||||
title: "Test task",
|
||||
description: "Task description",
|
||||
column: "todo" as Column,
|
||||
@@ -69,7 +69,7 @@ function renderCard(
|
||||
) {
|
||||
const props: ComponentProps<typeof InlineCreateCard> = {
|
||||
tasks,
|
||||
onSubmit: vi.fn().mockResolvedValue({ id: "KB-001" } as Task),
|
||||
onSubmit: vi.fn().mockResolvedValue({ id: "FN-001" } as Task),
|
||||
onCancel: vi.fn(),
|
||||
addToast: vi.fn(),
|
||||
availableModels: MOCK_MODELS,
|
||||
@@ -144,7 +144,7 @@ describe("InlineCreateCard blur-to-cancel", () => {
|
||||
|
||||
describe("InlineCreateCard dep-dropdown focus retention", () => {
|
||||
const testTasks: Task[] = [
|
||||
createMockTask({ id: "KB-010", title: "Task A", description: "First task" }),
|
||||
createMockTask({ id: "FN-010", title: "Task A", description: "First task" }),
|
||||
];
|
||||
|
||||
it("dep-dropdown-item mouseDown calls preventDefault to retain focus", () => {
|
||||
@@ -386,9 +386,9 @@ describe("InlineCreateCard model selector", () => {
|
||||
|
||||
describe("InlineCreateCard dependency dropdown sort order", () => {
|
||||
const scrambledTasks: Task[] = [
|
||||
createMockTask({ id: "KB-001", title: "Oldest", description: "First", createdAt: "2026-01-01T00:00:00Z" }),
|
||||
createMockTask({ id: "KB-003", title: "Newest", description: "Third", createdAt: "2026-03-01T00:00:00Z" }),
|
||||
createMockTask({ id: "KB-002", title: "Middle", description: "Second", createdAt: "2026-02-01T00:00:00Z" }),
|
||||
createMockTask({ id: "FN-001", title: "Oldest", description: "First", createdAt: "2026-01-01T00:00:00Z" }),
|
||||
createMockTask({ id: "FN-003", title: "Newest", description: "Third", createdAt: "2026-03-01T00:00:00Z" }),
|
||||
createMockTask({ id: "FN-002", title: "Middle", description: "Second", createdAt: "2026-02-01T00:00:00Z" }),
|
||||
];
|
||||
|
||||
it("renders dependency dropdown items sorted newest-first by createdAt", () => {
|
||||
@@ -397,26 +397,26 @@ describe("InlineCreateCard dependency dropdown sort order", () => {
|
||||
const items = document.querySelectorAll(".dep-dropdown-item");
|
||||
expect(items).toHaveLength(3);
|
||||
const ids = Array.from(items).map((el) => el.querySelector(".dep-dropdown-id")?.textContent);
|
||||
expect(ids).toEqual(["KB-003", "KB-002", "KB-001"]);
|
||||
expect(ids).toEqual(["FN-003", "FN-002", "FN-001"]);
|
||||
});
|
||||
|
||||
it("preserves newest-first sort order when a search filter is applied", () => {
|
||||
renderCard(scrambledTasks);
|
||||
fireEvent.click(screen.getByText(/Deps/));
|
||||
const input = document.querySelector(".dep-dropdown-search") as HTMLInputElement;
|
||||
fireEvent.change(input, { target: { value: "KB-00" } });
|
||||
fireEvent.change(input, { target: { value: "FN-00" } });
|
||||
const items = document.querySelectorAll(".dep-dropdown-item");
|
||||
expect(items).toHaveLength(3);
|
||||
const ids = Array.from(items).map((el) => el.querySelector(".dep-dropdown-id")?.textContent);
|
||||
expect(ids).toEqual(["KB-003", "KB-002", "KB-001"]);
|
||||
expect(ids).toEqual(["FN-003", "FN-002", "FN-001"]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("InlineCreateCard dependency dropdown sort with identical timestamps", () => {
|
||||
const sameTimeTasks: Task[] = [
|
||||
createMockTask({ id: "KB-001", title: "First", description: "First task" }),
|
||||
createMockTask({ id: "KB-002", title: "Second", description: "Second task" }),
|
||||
createMockTask({ id: "KB-003", title: "Third", description: "Third task" }),
|
||||
createMockTask({ id: "FN-001", title: "First", description: "First task" }),
|
||||
createMockTask({ id: "FN-002", title: "Second", description: "Second task" }),
|
||||
createMockTask({ id: "FN-003", title: "Third", description: "Third task" }),
|
||||
];
|
||||
|
||||
it("renders tasks with identical createdAt sorted newest-ID-first (descending numeric ID)", () => {
|
||||
@@ -425,26 +425,26 @@ describe("InlineCreateCard dependency dropdown sort with identical timestamps",
|
||||
const items = document.querySelectorAll(".dep-dropdown-item");
|
||||
expect(items).toHaveLength(3);
|
||||
const ids = Array.from(items).map((el) => el.querySelector(".dep-dropdown-id")?.textContent);
|
||||
expect(ids).toEqual(["KB-003", "KB-002", "KB-001"]);
|
||||
expect(ids).toEqual(["FN-003", "FN-002", "FN-001"]);
|
||||
});
|
||||
|
||||
it("preserves newest-ID-first order when search filter is applied with identical timestamps", () => {
|
||||
renderCard(sameTimeTasks);
|
||||
fireEvent.click(screen.getByText(/Deps/));
|
||||
const input = document.querySelector(".dep-dropdown-search") as HTMLInputElement;
|
||||
fireEvent.change(input, { target: { value: "KB-00" } });
|
||||
fireEvent.change(input, { target: { value: "FN-00" } });
|
||||
const items = document.querySelectorAll(".dep-dropdown-item");
|
||||
expect(items).toHaveLength(3);
|
||||
const ids = Array.from(items).map((el) => el.querySelector(".dep-dropdown-id")?.textContent);
|
||||
expect(ids).toEqual(["KB-003", "KB-002", "KB-001"]);
|
||||
expect(ids).toEqual(["FN-003", "FN-002", "FN-001"]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("InlineCreateCard dependency dropdown search", () => {
|
||||
const testTasks: Task[] = [
|
||||
createMockTask({ id: "KB-001", title: "Fix login", description: "Login page broken", createdAt: "2026-01-01T00:00:00Z" }),
|
||||
createMockTask({ id: "KB-002", title: "Add dark mode", description: "Theme support", createdAt: "2026-02-01T00:00:00Z" }),
|
||||
createMockTask({ id: "KB-003", title: "Refactor API", description: "Clean up endpoints", createdAt: "2026-03-01T00:00:00Z" }),
|
||||
createMockTask({ id: "FN-001", title: "Fix login", description: "Login page broken", createdAt: "2026-01-01T00:00:00Z" }),
|
||||
createMockTask({ id: "FN-002", title: "Add dark mode", description: "Theme support", createdAt: "2026-02-01T00:00:00Z" }),
|
||||
createMockTask({ id: "FN-003", title: "Refactor API", description: "Clean up endpoints", createdAt: "2026-03-01T00:00:00Z" }),
|
||||
];
|
||||
|
||||
it("shows search input when dropdown is opened", () => {
|
||||
@@ -463,7 +463,7 @@ describe("InlineCreateCard dependency dropdown search", () => {
|
||||
|
||||
const items = document.querySelectorAll(".dep-dropdown-item");
|
||||
expect(items).toHaveLength(1);
|
||||
expect(items[0].querySelector(".dep-dropdown-id")?.textContent).toBe("KB-002");
|
||||
expect(items[0].querySelector(".dep-dropdown-id")?.textContent).toBe("FN-002");
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -3,7 +3,7 @@ import { render, screen } from "@testing-library/react";
|
||||
import { MergeDetails } from "../MergeDetails";
|
||||
|
||||
const makeTask = (overrides: any = {}) => ({
|
||||
id: "KB-001",
|
||||
id: "FN-001",
|
||||
description: "Task",
|
||||
column: "done",
|
||||
dependencies: [],
|
||||
|
||||
@@ -22,7 +22,7 @@ const mockFetchModels = api.fetchModels as ReturnType<typeof vi.fn>;
|
||||
const mockUpdateTask = api.updateTask as ReturnType<typeof vi.fn>;
|
||||
|
||||
const FAKE_TASK: Task = {
|
||||
id: "KB-001",
|
||||
id: "FN-001",
|
||||
description: "Test task",
|
||||
column: "todo",
|
||||
dependencies: [],
|
||||
@@ -202,7 +202,7 @@ describe("ModelSelectorTab", () => {
|
||||
await selectOption("Executor Model", "Claude Sonnet 4.5");
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockUpdateTask).toHaveBeenNthCalledWith(1, "KB-001", {
|
||||
expect(mockUpdateTask).toHaveBeenNthCalledWith(1, "FN-001", {
|
||||
modelProvider: "anthropic",
|
||||
modelId: "claude-sonnet-4-5",
|
||||
validatorModelProvider: null,
|
||||
@@ -213,7 +213,7 @@ describe("ModelSelectorTab", () => {
|
||||
await selectOption("Validator Model", "GPT-4o");
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockUpdateTask).toHaveBeenNthCalledWith(2, "KB-001", {
|
||||
expect(mockUpdateTask).toHaveBeenNthCalledWith(2, "FN-001", {
|
||||
modelProvider: "anthropic",
|
||||
modelId: "claude-sonnet-4-5",
|
||||
validatorModelProvider: "openai",
|
||||
@@ -239,7 +239,7 @@ describe("ModelSelectorTab", () => {
|
||||
await selectOption("Executor Model", "Claude Sonnet 4.5");
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockUpdateTask).toHaveBeenCalledWith("KB-001", {
|
||||
expect(mockUpdateTask).toHaveBeenCalledWith("FN-001", {
|
||||
modelProvider: "anthropic",
|
||||
modelId: "claude-sonnet-4-5",
|
||||
validatorModelProvider: "openai",
|
||||
@@ -268,7 +268,7 @@ describe("ModelSelectorTab", () => {
|
||||
await user.click(getUseDefaultOption());
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockUpdateTask).toHaveBeenCalledWith("KB-001", {
|
||||
expect(mockUpdateTask).toHaveBeenCalledWith("FN-001", {
|
||||
modelProvider: null,
|
||||
modelId: null,
|
||||
validatorModelProvider: null,
|
||||
@@ -294,7 +294,7 @@ describe("ModelSelectorTab", () => {
|
||||
await selectOption("Validator Model", "GPT-4o");
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockUpdateTask).toHaveBeenCalledWith("KB-001", {
|
||||
expect(mockUpdateTask).toHaveBeenCalledWith("FN-001", {
|
||||
modelProvider: "anthropic",
|
||||
modelId: "claude-sonnet-4-5",
|
||||
validatorModelProvider: "openai",
|
||||
@@ -323,7 +323,7 @@ describe("ModelSelectorTab", () => {
|
||||
await user.click(getUseDefaultOption());
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockUpdateTask).toHaveBeenCalledWith("KB-001", {
|
||||
expect(mockUpdateTask).toHaveBeenCalledWith("FN-001", {
|
||||
modelProvider: null,
|
||||
modelId: null,
|
||||
validatorModelProvider: null,
|
||||
@@ -646,7 +646,7 @@ describe("ModelSelectorTab", () => {
|
||||
expect(screen.queryByPlaceholderText("Filter models…")).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
expect(mockUpdateTask).toHaveBeenCalledWith("KB-001", {
|
||||
expect(mockUpdateTask).toHaveBeenCalledWith("FN-001", {
|
||||
modelProvider: "anthropic",
|
||||
modelId: "claude-sonnet-4-5",
|
||||
validatorModelProvider: null,
|
||||
@@ -696,7 +696,7 @@ describe("ModelSelectorTab", () => {
|
||||
await user.click(screen.getByText("GPT-4o"));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockUpdateTask).toHaveBeenCalledWith("KB-001", {
|
||||
expect(mockUpdateTask).toHaveBeenCalledWith("FN-001", {
|
||||
modelProvider: "openai",
|
||||
modelId: "gpt-4o",
|
||||
validatorModelProvider: null,
|
||||
|
||||
@@ -41,7 +41,7 @@ function renderNewTaskModal(props = {}) {
|
||||
isOpen: true,
|
||||
onClose: vi.fn(),
|
||||
tasks: [] as Task[],
|
||||
onCreateTask: vi.fn().mockResolvedValue({ id: "KB-001" }),
|
||||
onCreateTask: vi.fn().mockResolvedValue({ id: "FN-001" }),
|
||||
addToast: vi.fn(),
|
||||
};
|
||||
const mergedProps = { ...defaultProps, ...props };
|
||||
@@ -119,7 +119,7 @@ describe("NewTaskModal", () => {
|
||||
|
||||
it("shows success toast after creation", async () => {
|
||||
const { props } = renderNewTaskModal({
|
||||
onCreateTask: vi.fn().mockResolvedValue({ id: "KB-042" }),
|
||||
onCreateTask: vi.fn().mockResolvedValue({ id: "FN-042" }),
|
||||
});
|
||||
|
||||
const descTextarea = screen.getByLabelText(/Description/i);
|
||||
|
||||
@@ -19,7 +19,7 @@ const mockPrInfo = {
|
||||
number: 42,
|
||||
status: "open" as const,
|
||||
title: "Fix the bug",
|
||||
headBranch: "kb/kb-001",
|
||||
headBranch: "fusion/fn-001",
|
||||
baseBranch: "main",
|
||||
commentCount: 3,
|
||||
lastCommentAt: "2026-01-01T00:00:00.000Z",
|
||||
@@ -34,7 +34,7 @@ describe("PrSection", () => {
|
||||
it("shows create PR button when GitHub token is available", () => {
|
||||
render(
|
||||
<PrSection
|
||||
taskId="KB-001"
|
||||
taskId="FN-001"
|
||||
hasGitHubToken={true}
|
||||
onPrCreated={mockOnPrCreated}
|
||||
onPrUpdated={mockOnPrUpdated}
|
||||
@@ -48,7 +48,7 @@ describe("PrSection", () => {
|
||||
it("shows disabled button and hint when GitHub token is missing", () => {
|
||||
render(
|
||||
<PrSection
|
||||
taskId="KB-001"
|
||||
taskId="FN-001"
|
||||
hasGitHubToken={false}
|
||||
onPrCreated={mockOnPrCreated}
|
||||
onPrUpdated={mockOnPrUpdated}
|
||||
@@ -64,7 +64,7 @@ describe("PrSection", () => {
|
||||
it("shows create form when clicking create button", () => {
|
||||
render(
|
||||
<PrSection
|
||||
taskId="KB-001"
|
||||
taskId="FN-001"
|
||||
hasGitHubToken={true}
|
||||
onPrCreated={mockOnPrCreated}
|
||||
onPrUpdated={mockOnPrUpdated}
|
||||
@@ -82,7 +82,7 @@ describe("PrSection", () => {
|
||||
it("hides form when clicking cancel", () => {
|
||||
render(
|
||||
<PrSection
|
||||
taskId="KB-001"
|
||||
taskId="FN-001"
|
||||
hasGitHubToken={true}
|
||||
onPrCreated={mockOnPrCreated}
|
||||
onPrUpdated={mockOnPrUpdated}
|
||||
@@ -101,7 +101,7 @@ describe("PrSection", () => {
|
||||
|
||||
render(
|
||||
<PrSection
|
||||
taskId="KB-001"
|
||||
taskId="FN-001"
|
||||
hasGitHubToken={true}
|
||||
onPrCreated={mockOnPrCreated}
|
||||
onPrUpdated={mockOnPrUpdated}
|
||||
@@ -116,7 +116,7 @@ describe("PrSection", () => {
|
||||
fireEvent.click(screen.getByText("Create PR"));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(createPr).toHaveBeenCalledWith("KB-001", {
|
||||
expect(createPr).toHaveBeenCalledWith("FN-001", {
|
||||
title: "My PR Title",
|
||||
body: undefined,
|
||||
});
|
||||
@@ -131,7 +131,7 @@ describe("PrSection", () => {
|
||||
|
||||
render(
|
||||
<PrSection
|
||||
taskId="KB-001"
|
||||
taskId="FN-001"
|
||||
hasGitHubToken={true}
|
||||
onPrCreated={mockOnPrCreated}
|
||||
onPrUpdated={mockOnPrUpdated}
|
||||
@@ -155,7 +155,7 @@ describe("PrSection", () => {
|
||||
it("displays PR info for open PR", () => {
|
||||
render(
|
||||
<PrSection
|
||||
taskId="KB-001"
|
||||
taskId="FN-001"
|
||||
prInfo={mockPrInfo}
|
||||
hasGitHubToken={true}
|
||||
onPrCreated={mockOnPrCreated}
|
||||
@@ -173,7 +173,7 @@ describe("PrSection", () => {
|
||||
it("shows correct status badge for merged PR", () => {
|
||||
render(
|
||||
<PrSection
|
||||
taskId="KB-001"
|
||||
taskId="FN-001"
|
||||
prInfo={{ ...mockPrInfo, status: "merged" }}
|
||||
hasGitHubToken={true}
|
||||
onPrCreated={mockOnPrCreated}
|
||||
@@ -189,7 +189,7 @@ describe("PrSection", () => {
|
||||
it("shows correct status badge for closed PR", () => {
|
||||
render(
|
||||
<PrSection
|
||||
taskId="KB-001"
|
||||
taskId="FN-001"
|
||||
prInfo={{ ...mockPrInfo, status: "closed" }}
|
||||
hasGitHubToken={true}
|
||||
onPrCreated={mockOnPrCreated}
|
||||
@@ -204,7 +204,7 @@ describe("PrSection", () => {
|
||||
it("displays comment count when PR has comments", () => {
|
||||
render(
|
||||
<PrSection
|
||||
taskId="KB-001"
|
||||
taskId="FN-001"
|
||||
prInfo={{ ...mockPrInfo, commentCount: 5 }}
|
||||
hasGitHubToken={true}
|
||||
onPrCreated={mockOnPrCreated}
|
||||
@@ -230,7 +230,7 @@ describe("PrSection", () => {
|
||||
|
||||
render(
|
||||
<PrSection
|
||||
taskId="KB-001"
|
||||
taskId="FN-001"
|
||||
prInfo={mockPrInfo}
|
||||
hasGitHubToken={true}
|
||||
onPrCreated={mockOnPrCreated}
|
||||
@@ -243,7 +243,7 @@ describe("PrSection", () => {
|
||||
fireEvent.click(refreshButton);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(refreshPrStatus).toHaveBeenCalledWith("KB-001");
|
||||
expect(refreshPrStatus).toHaveBeenCalledWith("FN-001");
|
||||
});
|
||||
|
||||
expect(mockOnPrUpdated).toHaveBeenCalledWith(updatedPr);
|
||||
@@ -255,7 +255,7 @@ describe("PrSection", () => {
|
||||
|
||||
render(
|
||||
<PrSection
|
||||
taskId="KB-001"
|
||||
taskId="FN-001"
|
||||
prInfo={mockPrInfo}
|
||||
hasGitHubToken={true}
|
||||
onPrCreated={mockOnPrCreated}
|
||||
@@ -275,7 +275,7 @@ describe("PrSection", () => {
|
||||
it("shows automatic PR creation message while PR-first automation is creating a PR", () => {
|
||||
render(
|
||||
<PrSection
|
||||
taskId="KB-001"
|
||||
taskId="FN-001"
|
||||
automationStatus="creating-pr"
|
||||
hasGitHubToken={true}
|
||||
onPrCreated={mockOnPrCreated}
|
||||
@@ -300,7 +300,7 @@ describe("PrSection", () => {
|
||||
|
||||
render(
|
||||
<PrSection
|
||||
taskId="KB-001"
|
||||
taskId="FN-001"
|
||||
prInfo={mockPrInfo}
|
||||
automationStatus="awaiting-pr-checks"
|
||||
hasGitHubToken={true}
|
||||
|
||||
@@ -22,7 +22,7 @@ const MOCK_MODELS = [
|
||||
|
||||
const mockTasks: Task[] = [
|
||||
{
|
||||
id: "KB-001",
|
||||
id: "FN-001",
|
||||
title: "Test task 1",
|
||||
description: "First test task",
|
||||
column: "todo",
|
||||
@@ -34,7 +34,7 @@ const mockTasks: Task[] = [
|
||||
updatedAt: "2026-01-01T00:00:00Z",
|
||||
},
|
||||
{
|
||||
id: "KB-002",
|
||||
id: "FN-002",
|
||||
title: "Test task 2",
|
||||
description: "Second test task",
|
||||
column: "todo",
|
||||
@@ -529,7 +529,7 @@ describe("QuickEntryBox", () => {
|
||||
expect(props.onCreate).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
description: "Task with deps",
|
||||
dependencies: expect.arrayContaining(["KB-002"]), // Most recent task
|
||||
dependencies: expect.arrayContaining(["FN-002"]), // Most recent task
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
@@ -14,7 +14,7 @@ const mockAddToast = vi.fn();
|
||||
|
||||
function makeTask(overrides: Partial<TaskDetail> = {}): TaskDetail {
|
||||
return {
|
||||
id: "KB-001",
|
||||
id: "FN-001",
|
||||
description: "Test task",
|
||||
column: "in-progress",
|
||||
dependencies: [],
|
||||
@@ -141,7 +141,7 @@ describe("SteeringTab", () => {
|
||||
fireEvent.click(button);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockApi).toHaveBeenCalledWith("KB-001", "New comment");
|
||||
expect(mockApi).toHaveBeenCalledWith("FN-001", "New comment");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -168,7 +168,7 @@ describe("SteeringTab", () => {
|
||||
fireEvent.keyDown(textarea, { key: "Enter", ctrlKey: true });
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockApi).toHaveBeenCalledWith("KB-001", "Keyboard comment");
|
||||
expect(mockApi).toHaveBeenCalledWith("FN-001", "Keyboard comment");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -195,7 +195,7 @@ describe("SteeringTab", () => {
|
||||
fireEvent.keyDown(textarea, { key: "Enter", metaKey: true });
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockApi).toHaveBeenCalledWith("KB-001", "Mac keyboard comment");
|
||||
expect(mockApi).toHaveBeenCalledWith("FN-001", "Mac keyboard comment");
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -61,7 +61,7 @@ function computeCardClass(opts: { dragging?: boolean; queued?: boolean; status?:
|
||||
|
||||
describe("TaskCard memoization", () => {
|
||||
const createTask = (overrides: Partial<Task> = {}): Task => ({
|
||||
id: "KB-001",
|
||||
id: "FN-001",
|
||||
description: "Test task",
|
||||
column: "todo",
|
||||
dependencies: [],
|
||||
@@ -144,11 +144,11 @@ describe("TaskCard memoization", () => {
|
||||
}
|
||||
|
||||
const MemoizedProbe = React.memo(MemoProbe);
|
||||
const { rerender } = render(<MemoizedProbe task={createTask({ dependencies: ["KB-001"] })} />);
|
||||
const { rerender } = render(<MemoizedProbe task={createTask({ dependencies: ["FN-001"] })} />);
|
||||
|
||||
expect(cardRenderSpy).toHaveBeenCalledTimes(1);
|
||||
|
||||
rerender(<MemoizedProbe task={createTask({ dependencies: ["KB-001", "KB-002"] })} />);
|
||||
rerender(<MemoizedProbe task={createTask({ dependencies: ["FN-001", "FN-002"] })} />);
|
||||
|
||||
expect(cardRenderSpy).toHaveBeenCalledTimes(2);
|
||||
expect(screen.getAllByTitle(/Click to view/)).toHaveLength(2);
|
||||
@@ -170,7 +170,7 @@ describe("TaskCard memoization", () => {
|
||||
url: "https://github.com/example/repo/pull/42",
|
||||
status: "open" as const,
|
||||
title: "Initial PR",
|
||||
headBranch: "kb/kb-129",
|
||||
headBranch: "fusion/fn-129",
|
||||
baseBranch: "main",
|
||||
lastCheckedAt: "2026-01-01T00:00:00Z",
|
||||
};
|
||||
@@ -237,10 +237,10 @@ describe("TaskCard memoization", () => {
|
||||
|
||||
expect(cardRenderSpy).toHaveBeenCalledTimes(1);
|
||||
|
||||
rerender(<MemoizedProbe task={createTask({ blockedBy: "KB-777" })} />);
|
||||
rerender(<MemoizedProbe task={createTask({ blockedBy: "FN-777" })} />);
|
||||
|
||||
expect(cardRenderSpy).toHaveBeenCalledTimes(2);
|
||||
expect(screen.getByText("KB-777")).toBeDefined();
|
||||
expect(screen.getByText("FN-777")).toBeDefined();
|
||||
});
|
||||
|
||||
it("re-renders when queued state changes", () => {
|
||||
@@ -426,11 +426,11 @@ describe("TaskCard dependency tooltip", () => {
|
||||
}
|
||||
|
||||
it("returns comma-separated dependency IDs when dependencies are present", () => {
|
||||
expect(computeDepTooltip(["KB-001", "KB-042"])).toBe("KB-001, KB-042");
|
||||
expect(computeDepTooltip(["FN-001", "FN-042"])).toBe("FN-001, FN-042");
|
||||
});
|
||||
|
||||
it("returns single dependency ID when only one dependency", () => {
|
||||
expect(computeDepTooltip(["KB-010"])).toBe("KB-010");
|
||||
expect(computeDepTooltip(["FN-010"])).toBe("FN-010");
|
||||
});
|
||||
|
||||
it("returns undefined when dependencies array is empty", () => {
|
||||
@@ -438,12 +438,12 @@ describe("TaskCard dependency tooltip", () => {
|
||||
});
|
||||
|
||||
it("handles many dependencies", () => {
|
||||
const deps = ["KB-001", "KB-002", "KB-003", "KB-004"];
|
||||
expect(computeDepTooltip(deps)).toBe("KB-001, KB-002, KB-003, KB-004");
|
||||
const deps = ["FN-001", "FN-002", "FN-003", "FN-004"];
|
||||
expect(computeDepTooltip(deps)).toBe("FN-001, FN-002, FN-003, FN-004");
|
||||
});
|
||||
|
||||
it("data-tooltip attribute contains dependency IDs as a readable string", () => {
|
||||
const deps = ["KB-005", "KB-012"];
|
||||
const deps = ["FN-005", "FN-012"];
|
||||
const tooltip = computeDepTooltip(deps);
|
||||
expect(tooltip).toBeDefined();
|
||||
// Each dependency ID should appear in the tooltip
|
||||
@@ -466,7 +466,7 @@ describe("TaskCard file-scope overlap badge logic", () => {
|
||||
}
|
||||
|
||||
it("shows scope badge when blockedBy is set", () => {
|
||||
expect(shouldShowScopeBadge("KB-003")).toBe(true);
|
||||
expect(shouldShowScopeBadge("FN-003")).toBe(true);
|
||||
});
|
||||
|
||||
it("does NOT show scope badge when blockedBy is undefined", () => {
|
||||
@@ -474,7 +474,7 @@ describe("TaskCard file-scope overlap badge logic", () => {
|
||||
});
|
||||
|
||||
it("shows card-meta when blockedBy is set even with no deps or queued status", () => {
|
||||
expect(shouldShowCardMeta({ blockedBy: "KB-003" })).toBe(true);
|
||||
expect(shouldShowCardMeta({ blockedBy: "FN-003" })).toBe(true);
|
||||
});
|
||||
|
||||
it("does NOT show card-meta when no deps, not queued, and no blockedBy", () => {
|
||||
@@ -487,7 +487,7 @@ describe("TaskCard file-scope overlap badge logic", () => {
|
||||
}
|
||||
|
||||
it("generates correct tooltip text", () => {
|
||||
expect(computeScopeTooltip("KB-005")).toBe("Blocked by KB-005 (file overlap)");
|
||||
expect(computeScopeTooltip("FN-005")).toBe("Blocked by KB-005 (file overlap)");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -539,7 +539,7 @@ describe("TaskCard queued badge logic", () => {
|
||||
*/
|
||||
function makeTask(overrides: Partial<Task> = {}): Task {
|
||||
return {
|
||||
id: "KB-099",
|
||||
id: "FN-099",
|
||||
description: "Test task",
|
||||
column: "in-progress" as Column,
|
||||
dependencies: [],
|
||||
@@ -560,10 +560,10 @@ describe("TaskCard clickable dependencies", () => {
|
||||
});
|
||||
|
||||
it("renders dependency badges as clickable when dependencies exist", () => {
|
||||
const task = makeTask({ dependencies: ["KB-001", "KB-002"] });
|
||||
const task = makeTask({ dependencies: ["FN-001", "FN-002"] });
|
||||
const allTasks: Task[] = [
|
||||
makeTask({ id: "KB-001", description: "Dep 1" }),
|
||||
makeTask({ id: "KB-002", description: "Dep 2" }),
|
||||
makeTask({ id: "FN-001", description: "Dep 1" }),
|
||||
makeTask({ id: "FN-002", description: "Dep 2" }),
|
||||
];
|
||||
|
||||
render(
|
||||
@@ -599,15 +599,15 @@ describe("TaskCard clickable dependencies", () => {
|
||||
const { fetchTaskDetail } = await import("../../api");
|
||||
const mockFetch = vi.mocked(fetchTaskDetail);
|
||||
const mockDetail: TaskDetail = {
|
||||
...makeTask({ id: "KB-001", description: "Dep 1" }),
|
||||
...makeTask({ id: "FN-001", description: "Dep 1" }),
|
||||
prompt: "",
|
||||
attachments: [],
|
||||
};
|
||||
mockFetch.mockResolvedValueOnce(mockDetail);
|
||||
const onOpenDetail = vi.fn();
|
||||
|
||||
const task = makeTask({ dependencies: ["KB-001"] });
|
||||
const allTasks: Task[] = [makeTask({ id: "KB-001", description: "Dep 1" })];
|
||||
const task = makeTask({ dependencies: ["FN-001"] });
|
||||
const allTasks: Task[] = [makeTask({ id: "FN-001", description: "Dep 1" })];
|
||||
|
||||
render(
|
||||
<TaskCard
|
||||
@@ -621,7 +621,7 @@ describe("TaskCard clickable dependencies", () => {
|
||||
fireEvent.click(depBadge);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockFetch).toHaveBeenCalledWith("KB-001");
|
||||
expect(mockFetch).toHaveBeenCalledWith("FN-001");
|
||||
expect(onOpenDetail).toHaveBeenCalledWith(mockDetail);
|
||||
});
|
||||
});
|
||||
@@ -633,7 +633,7 @@ describe("TaskCard clickable dependencies", () => {
|
||||
const onOpenDetail = vi.fn();
|
||||
const addToast = vi.fn();
|
||||
|
||||
const task = makeTask({ dependencies: ["KB-001"] });
|
||||
const task = makeTask({ dependencies: ["FN-001"] });
|
||||
|
||||
render(
|
||||
<TaskCard
|
||||
@@ -658,7 +658,7 @@ describe("TaskCard clickable dependencies", () => {
|
||||
mockFetch.mockRejectedValueOnce(new Error("Stop here"));
|
||||
const addToast = vi.fn();
|
||||
|
||||
const task = makeTask({ dependencies: ["KB-001"] });
|
||||
const task = makeTask({ dependencies: ["FN-001"] });
|
||||
|
||||
const { container } = render(
|
||||
<TaskCard
|
||||
@@ -1001,7 +1001,7 @@ describe("TaskCard inline editing", () => {
|
||||
/>
|
||||
);
|
||||
|
||||
const card = document.querySelector('[data-id="KB-099"]');
|
||||
const card = document.querySelector('[data-id="FN-099"]');
|
||||
expect(card).toBeDefined();
|
||||
fireEvent.doubleClick(card!);
|
||||
|
||||
@@ -1046,7 +1046,7 @@ describe("TaskCard inline editing", () => {
|
||||
/>
|
||||
);
|
||||
|
||||
const card = document.querySelector('[data-id="KB-099"]');
|
||||
const card = document.querySelector('[data-id="FN-099"]');
|
||||
fireEvent.doubleClick(card!);
|
||||
|
||||
// Should NOT show editing UI
|
||||
@@ -1067,7 +1067,7 @@ describe("TaskCard inline editing", () => {
|
||||
);
|
||||
|
||||
// Enter edit mode
|
||||
const card = document.querySelector('[data-id="KB-099"]');
|
||||
const card = document.querySelector('[data-id="FN-099"]');
|
||||
fireEvent.doubleClick(card!);
|
||||
|
||||
const titleInput = screen.getByPlaceholderText(/Task title/i) as HTMLInputElement;
|
||||
@@ -1095,7 +1095,7 @@ describe("TaskCard inline editing", () => {
|
||||
);
|
||||
|
||||
// Enter edit mode
|
||||
const card = document.querySelector('[data-id="KB-099"]');
|
||||
const card = document.querySelector('[data-id="FN-099"]');
|
||||
await user.dblClick(card!);
|
||||
|
||||
const titleInput = screen.getByPlaceholderText(/Task title/i);
|
||||
@@ -1126,7 +1126,7 @@ describe("TaskCard inline editing", () => {
|
||||
);
|
||||
|
||||
// Enter edit mode
|
||||
const card = document.querySelector('[data-id="KB-099"]');
|
||||
const card = document.querySelector('[data-id="FN-099"]');
|
||||
fireEvent.doubleClick(card!);
|
||||
|
||||
const descTextarea = screen.getByPlaceholderText(/Task description/i) as HTMLTextAreaElement;
|
||||
@@ -1136,7 +1136,7 @@ describe("TaskCard inline editing", () => {
|
||||
fireEvent.keyDown(descTextarea, { key: "Enter", shiftKey: false });
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockUpdateTask).toHaveBeenCalledWith("KB-099", {
|
||||
expect(mockUpdateTask).toHaveBeenCalledWith("FN-099", {
|
||||
title: "Title",
|
||||
description: "New Desc",
|
||||
});
|
||||
@@ -1156,7 +1156,7 @@ describe("TaskCard inline editing", () => {
|
||||
);
|
||||
|
||||
// Enter edit mode
|
||||
const card = document.querySelector('[data-id="KB-099"]');
|
||||
const card = document.querySelector('[data-id="FN-099"]');
|
||||
fireEvent.doubleClick(card!);
|
||||
|
||||
const titleInput = screen.getByPlaceholderText(/Task title/i) as HTMLInputElement;
|
||||
@@ -1182,7 +1182,7 @@ describe("TaskCard inline editing", () => {
|
||||
);
|
||||
|
||||
// Enter edit mode
|
||||
const card = document.querySelector('[data-id="KB-099"]');
|
||||
const card = document.querySelector('[data-id="FN-099"]');
|
||||
fireEvent.doubleClick(card!);
|
||||
|
||||
const descTextarea = screen.getByPlaceholderText(/Task description/i) as HTMLTextAreaElement;
|
||||
@@ -1213,7 +1213,7 @@ describe("TaskCard inline editing", () => {
|
||||
);
|
||||
|
||||
// Enter edit mode
|
||||
const card = document.querySelector('[data-id="KB-099"]');
|
||||
const card = document.querySelector('[data-id="FN-099"]');
|
||||
fireEvent.doubleClick(card!);
|
||||
|
||||
const descTextarea = screen.getByPlaceholderText(/Task description/i) as HTMLTextAreaElement;
|
||||
@@ -1251,7 +1251,7 @@ describe("TaskCard inline editing", () => {
|
||||
);
|
||||
|
||||
// Enter edit mode
|
||||
const card = document.querySelector('[data-id="KB-099"]');
|
||||
const card = document.querySelector('[data-id="FN-099"]');
|
||||
fireEvent.doubleClick(card!);
|
||||
|
||||
const descTextarea = screen.getByPlaceholderText(/Task description/i) as HTMLTextAreaElement;
|
||||
@@ -1281,7 +1281,7 @@ describe("TaskCard inline editing", () => {
|
||||
);
|
||||
|
||||
// Enter edit mode
|
||||
const card = document.querySelector('[data-id="KB-099"]') as HTMLElement;
|
||||
const card = document.querySelector('[data-id="FN-099"]') as HTMLElement;
|
||||
fireEvent.doubleClick(card);
|
||||
|
||||
// Card should have editing class
|
||||
@@ -1300,7 +1300,7 @@ describe("TaskCard inline editing", () => {
|
||||
/>
|
||||
);
|
||||
|
||||
const card = document.querySelector('[data-id="KB-099"]') as HTMLElement;
|
||||
const card = document.querySelector('[data-id="FN-099"]') as HTMLElement;
|
||||
expect(card.classList.contains("card-editing")).toBe(false);
|
||||
|
||||
fireEvent.doubleClick(card);
|
||||
@@ -2038,7 +2038,7 @@ describe("TaskCard GitHub badges", () => {
|
||||
mockUseBadgeWebSocket.mockReturnValue({
|
||||
badgeUpdates: new Map([
|
||||
[
|
||||
"KB-099",
|
||||
"FN-099",
|
||||
{
|
||||
prInfo: {
|
||||
url: "https://github.com/owner/repo/pull/42",
|
||||
@@ -2088,7 +2088,7 @@ describe("TaskCard GitHub badges", () => {
|
||||
mockUseBadgeWebSocket.mockReturnValue({
|
||||
badgeUpdates: new Map([
|
||||
[
|
||||
"KB-099",
|
||||
"FN-099",
|
||||
{
|
||||
prInfo: {
|
||||
url: "https://github.com/owner/repo/pull/42",
|
||||
@@ -2137,7 +2137,7 @@ describe("TaskCard GitHub badges", () => {
|
||||
mockUseBadgeWebSocket.mockReturnValue({
|
||||
badgeUpdates: new Map([
|
||||
[
|
||||
"KB-099",
|
||||
"FN-099",
|
||||
{
|
||||
issueInfo: {
|
||||
url: "https://github.com/owner/repo/issues/123",
|
||||
@@ -2218,11 +2218,11 @@ describe("TaskCard GitHub badges", () => {
|
||||
/>
|
||||
);
|
||||
|
||||
expect(subscribeToBadge).toHaveBeenCalledWith("KB-099");
|
||||
expect(subscribeToBadge).toHaveBeenCalledWith("FN-099");
|
||||
|
||||
unmount();
|
||||
|
||||
expect(unsubscribeFromBadge).toHaveBeenCalledWith("KB-099");
|
||||
expect(unsubscribeFromBadge).toHaveBeenCalledWith("FN-099");
|
||||
});
|
||||
|
||||
it("waits for viewport intersection before subscribing and unsubscribes when leaving", () => {
|
||||
@@ -2281,13 +2281,13 @@ describe("TaskCard GitHub badges", () => {
|
||||
observers[0].callback([{ isIntersecting: true } as IntersectionObserverEntry], {} as IntersectionObserver);
|
||||
});
|
||||
|
||||
expect(subscribeToBadge).toHaveBeenCalledWith("KB-099");
|
||||
expect(subscribeToBadge).toHaveBeenCalledWith("FN-099");
|
||||
|
||||
act(() => {
|
||||
observers[0].callback([{ isIntersecting: false } as IntersectionObserverEntry], {} as IntersectionObserver);
|
||||
});
|
||||
|
||||
expect(unsubscribeFromBadge).toHaveBeenCalledWith("KB-099");
|
||||
expect(unsubscribeFromBadge).toHaveBeenCalledWith("FN-099");
|
||||
} finally {
|
||||
(globalThis as unknown as { IntersectionObserver: typeof IntersectionObserver | undefined }).IntersectionObserver = originalIntersectionObserver;
|
||||
}
|
||||
@@ -2310,7 +2310,7 @@ describe("TaskCard expand button", () => {
|
||||
const { fetchTaskDetail } = await import("../../api");
|
||||
const mockFetch = vi.mocked(fetchTaskDetail);
|
||||
const mockDetail: TaskDetail = {
|
||||
...makeTask({ id: "KB-099" }),
|
||||
...makeTask({ id: "FN-099" }),
|
||||
prompt: "",
|
||||
attachments: [],
|
||||
};
|
||||
@@ -2327,6 +2327,9 @@ describe("TaskCard expand button", () => {
|
||||
/>
|
||||
);
|
||||
|
||||
const card = document.querySelector('[data-id="FN-099"]');
|
||||
expect(card).toBeDefined();
|
||||
|
||||
const expandButton = screen.getByRole("button", { name: /Open task details/i });
|
||||
expect(expandButton).toBeDefined();
|
||||
expect(expandButton.classList.contains("card-expand-btn")).toBe(true);
|
||||
@@ -2334,7 +2337,7 @@ describe("TaskCard expand button", () => {
|
||||
fireEvent.click(expandButton);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockFetch).toHaveBeenCalledWith("KB-099");
|
||||
expect(mockFetch).toHaveBeenCalledWith("FN-099");
|
||||
expect(onOpenDetail).toHaveBeenCalledWith(mockDetail);
|
||||
});
|
||||
});
|
||||
@@ -2352,7 +2355,7 @@ describe("TaskCard expand button", () => {
|
||||
/>
|
||||
);
|
||||
|
||||
const card = document.querySelector('[data-id="KB-099"]');
|
||||
const card = document.querySelector('[data-id="FN-099"]');
|
||||
expect(card).toBeDefined();
|
||||
|
||||
// Click on the card title (part of card body)
|
||||
@@ -2383,6 +2386,104 @@ describe("TaskCard expand button", () => {
|
||||
expect(expandButton.getAttribute("title")).toBe("Open task details");
|
||||
});
|
||||
|
||||
it("does NOT open modal during vertical scrolling", async () => {
|
||||
const onOpenDetail = vi.fn();
|
||||
|
||||
render(
|
||||
<TaskCard
|
||||
task={makeTask()}
|
||||
onOpenDetail={onOpenDetail}
|
||||
addToast={noopToast}
|
||||
/>
|
||||
);
|
||||
|
||||
const card = document.querySelector('[data-id="FN-099"]');
|
||||
expect(card).toBeDefined();
|
||||
|
||||
fireEvent.touchStart(card!, {
|
||||
touches: [{ clientX: 100, clientY: 100 }],
|
||||
});
|
||||
|
||||
fireEvent.touchMove(card!, {
|
||||
touches: [{ clientX: 100, clientY: 115 }],
|
||||
});
|
||||
|
||||
fireEvent.touchEnd(card!, {
|
||||
changedTouches: [{ clientX: 100, clientY: 115 }],
|
||||
target: card,
|
||||
});
|
||||
|
||||
await new Promise((resolve) => setTimeout(resolve, 50));
|
||||
|
||||
expect(onOpenDetail).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("does NOT open modal during horizontal scrolling", async () => {
|
||||
const onOpenDetail = vi.fn();
|
||||
|
||||
render(
|
||||
<TaskCard
|
||||
task={makeTask()}
|
||||
onOpenDetail={onOpenDetail}
|
||||
addToast={noopToast}
|
||||
/>
|
||||
);
|
||||
|
||||
const card = document.querySelector('[data-id="FN-099"]');
|
||||
expect(card).toBeDefined();
|
||||
|
||||
fireEvent.touchStart(card!, {
|
||||
touches: [{ clientX: 100, clientY: 100 }],
|
||||
});
|
||||
|
||||
fireEvent.touchMove(card!, {
|
||||
touches: [{ clientX: 115, clientY: 100 }],
|
||||
});
|
||||
|
||||
fireEvent.touchEnd(card!, {
|
||||
changedTouches: [{ clientX: 115, clientY: 100 }],
|
||||
target: card,
|
||||
});
|
||||
|
||||
await new Promise((resolve) => setTimeout(resolve, 50));
|
||||
|
||||
expect(onOpenDetail).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("does NOT open modal on long press (slow touch)", async () => {
|
||||
const onOpenDetail = vi.fn();
|
||||
|
||||
render(
|
||||
<TaskCard
|
||||
task={makeTask()}
|
||||
onOpenDetail={onOpenDetail}
|
||||
addToast={noopToast}
|
||||
/>
|
||||
);
|
||||
|
||||
const card = document.querySelector('[data-id="FN-099"]');
|
||||
expect(card).toBeDefined();
|
||||
|
||||
// Simulate long press: touchStart, wait > 300ms, then touchEnd
|
||||
fireEvent.touchStart(card!, {
|
||||
touches: [{ clientX: 100, clientY: 100 }],
|
||||
});
|
||||
|
||||
// Wait longer than tap threshold (300ms)
|
||||
await new Promise((resolve) => setTimeout(resolve, 350));
|
||||
|
||||
fireEvent.touchEnd(card!, {
|
||||
changedTouches: [{ clientX: 100, clientY: 100 }],
|
||||
target: card,
|
||||
});
|
||||
|
||||
// Wait for any async operations
|
||||
await new Promise((resolve) => setTimeout(resolve, 50));
|
||||
|
||||
// Modal should NOT have opened
|
||||
expect(onOpenDetail).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("expand button is present in all columns", () => {
|
||||
const columns: Column[] = ["triage", "todo", "in-progress", "in-review", "done", "archived"];
|
||||
|
||||
@@ -2409,7 +2510,7 @@ describe("TaskCard expand button", () => {
|
||||
const { fetchTaskDetail } = await import("../../api");
|
||||
const mockFetch = vi.mocked(fetchTaskDetail);
|
||||
const mockDetail: TaskDetail = {
|
||||
...makeTask({ id: "KB-099" }),
|
||||
...makeTask({ id: "FN-099" }),
|
||||
prompt: "",
|
||||
attachments: [],
|
||||
};
|
||||
@@ -2426,12 +2527,17 @@ describe("TaskCard expand button", () => {
|
||||
/>
|
||||
);
|
||||
|
||||
const card = document.querySelector('[data-id="FN-099"]');
|
||||
expect(card).toBeDefined();
|
||||
|
||||
const expandButton = screen.getByRole("button", { name: /Open task details/i });
|
||||
|
||||
// Click the expand button - should only trigger once
|
||||
fireEvent.click(expandButton);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockFetch).toHaveBeenCalledWith("FN-099");
|
||||
expect(onOpenDetail).toHaveBeenCalledWith(mockDetail);
|
||||
expect(onOpenDetail).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
@@ -2444,7 +2550,7 @@ describe("TaskCard title display", () => {
|
||||
const noopToast = vi.fn();
|
||||
|
||||
const makeTask = (overrides: Partial<Task> = {}): Task => ({
|
||||
id: "KB-001",
|
||||
id: "FN-001",
|
||||
description: "Test task",
|
||||
column: "todo",
|
||||
dependencies: [],
|
||||
@@ -2504,7 +2610,7 @@ describe("TaskCard title display", () => {
|
||||
);
|
||||
|
||||
// Look for the task ID within the card-title element specifically
|
||||
const cardTitle = screen.getByText("KB-001", { selector: ".card-title" });
|
||||
const cardTitle = screen.getByText("FN-001", { selector: ".card-title" });
|
||||
expect(cardTitle).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -11,7 +11,7 @@ vi.mock("../../api", () => ({
|
||||
import { addTaskComment, updateTaskComment, deleteTaskComment } from "../../api";
|
||||
|
||||
const makeTask = (overrides: any = {}) => ({
|
||||
id: "KB-001",
|
||||
id: "FN-001",
|
||||
description: "Task",
|
||||
column: "todo",
|
||||
dependencies: [],
|
||||
@@ -37,7 +37,7 @@ describe("TaskComments", () => {
|
||||
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"));
|
||||
await waitFor(() => expect(addTaskComment).toHaveBeenCalledWith("FN-001", "Hello", "user"));
|
||||
expect(onTaskUpdated).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
@@ -50,7 +50,7 @@ describe("TaskComments", () => {
|
||||
fireEvent.change(screen.getByDisplayValue("Original"), { target: { value: "Updated" } });
|
||||
fireEvent.click(screen.getByText("Save"));
|
||||
|
||||
await waitFor(() => expect(updateTaskComment).toHaveBeenCalledWith("KB-001", "c1", "Updated"));
|
||||
await waitFor(() => expect(updateTaskComment).toHaveBeenCalledWith("FN-001", "c1", "Updated"));
|
||||
expect(onTaskUpdated).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
@@ -61,7 +61,7 @@ describe("TaskComments", () => {
|
||||
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"));
|
||||
await waitFor(() => expect(deleteTaskComment).toHaveBeenCalledWith("FN-001", "c1"));
|
||||
expect(onTaskUpdated).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -22,7 +22,7 @@ vi.mock("../../hooks/useAgentLogs", () => ({
|
||||
|
||||
function makeTask(overrides: Partial<TaskDetail> = {}): TaskDetail {
|
||||
return {
|
||||
id: "KB-099",
|
||||
id: "FN-099",
|
||||
description: "Test task",
|
||||
column: "in-progress" as Column,
|
||||
dependencies: [],
|
||||
@@ -198,13 +198,13 @@ describe("TaskDetailModal", () => {
|
||||
/>,
|
||||
);
|
||||
|
||||
// The heading "KB-099" should be stripped from the markdown
|
||||
// The heading "FN-099" should be stripped from the markdown
|
||||
const markdownBody = container.querySelector(".markdown-body");
|
||||
expect(markdownBody?.innerHTML).not.toContain("KB-099");
|
||||
expect(markdownBody?.innerHTML).not.toContain("FN-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("KB-099");
|
||||
expect(container.querySelector(".detail-id")?.textContent).toBe("FN-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");
|
||||
@@ -243,7 +243,7 @@ describe("TaskDetailModal", () => {
|
||||
addToast={noop}
|
||||
/>,
|
||||
);
|
||||
expect(withTitle.querySelector(".detail-id")?.textContent).toBe("KB-099");
|
||||
expect(withTitle.querySelector(".detail-id")?.textContent).toBe("FN-099");
|
||||
|
||||
// Without title
|
||||
const { container: withoutTitle } = render(
|
||||
@@ -257,7 +257,7 @@ describe("TaskDetailModal", () => {
|
||||
addToast={noop}
|
||||
/>,
|
||||
);
|
||||
expect(withoutTitle.querySelector(".detail-id")?.textContent).toBe("KB-099");
|
||||
expect(withoutTitle.querySelector(".detail-id")?.textContent).toBe("FN-099");
|
||||
});
|
||||
|
||||
describe("paste image upload", () => {
|
||||
@@ -302,7 +302,7 @@ describe("TaskDetailModal", () => {
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockUpload).toHaveBeenCalledWith("KB-099", imageFile);
|
||||
expect(mockUpload).toHaveBeenCalledWith("FN-099", imageFile);
|
||||
expect(addToast).toHaveBeenCalledWith("Screenshot attached", "success");
|
||||
});
|
||||
});
|
||||
@@ -432,7 +432,7 @@ describe("TaskDetailModal", () => {
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockUpload).toHaveBeenCalledWith("KB-099", imageFile);
|
||||
expect(mockUpload).toHaveBeenCalledWith("FN-099", imageFile);
|
||||
expect(addToast).toHaveBeenCalledWith("Screenshot attached", "success");
|
||||
});
|
||||
});
|
||||
@@ -457,7 +457,7 @@ describe("TaskDetailModal", () => {
|
||||
it("renders dependency list when dependencies exist", () => {
|
||||
render(
|
||||
<TaskDetailModal
|
||||
task={makeTask({ dependencies: ["KB-001", "KB-002"] })}
|
||||
task={makeTask({ dependencies: ["FN-001", "FN-002"] })}
|
||||
onClose={noop}
|
||||
onMoveTask={noopMove}
|
||||
onDeleteTask={noopDelete}
|
||||
@@ -467,16 +467,16 @@ describe("TaskDetailModal", () => {
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(screen.getByText("KB-001")).toBeTruthy();
|
||||
expect(screen.getByText("KB-002")).toBeTruthy();
|
||||
expect(screen.getByText("FN-001")).toBeTruthy();
|
||||
expect(screen.getByText("FN-002")).toBeTruthy();
|
||||
expect(screen.queryByText("(no dependencies)")).toBeNull();
|
||||
});
|
||||
|
||||
it("can add a dependency via the dropdown", async () => {
|
||||
const { updateTask } = await import("../../api");
|
||||
const allTasks: Task[] = [
|
||||
{ id: "KB-001", description: "Dep 1", column: "todo" as Column, dependencies: [], steps: [], currentStep: 0, log: [], createdAt: "", updatedAt: "" },
|
||||
{ id: "KB-099", description: "Self", column: "in-progress" as Column, dependencies: [], steps: [], currentStep: 0, log: [], createdAt: "", updatedAt: "" },
|
||||
{ id: "FN-001", description: "Dep 1", column: "todo" as Column, dependencies: [], steps: [], currentStep: 0, log: [], createdAt: "", updatedAt: "" },
|
||||
{ id: "FN-099", description: "Self", column: "in-progress" as Column, dependencies: [], steps: [], currentStep: 0, log: [], createdAt: "", updatedAt: "" },
|
||||
];
|
||||
|
||||
render(
|
||||
@@ -496,13 +496,13 @@ describe("TaskDetailModal", () => {
|
||||
// Should show KB-001 in the dropdown but not KB-099 (self is excluded)
|
||||
const dropdown = document.querySelector(".dep-dropdown")!;
|
||||
expect(dropdown).toBeTruthy();
|
||||
expect(dropdown.textContent).toContain("KB-001");
|
||||
expect(dropdown.textContent).toContain("FN-001");
|
||||
expect(dropdown.querySelectorAll(".dep-dropdown-item")).toHaveLength(1);
|
||||
|
||||
fireEvent.click(screen.getByText("KB-001"));
|
||||
fireEvent.click(screen.getByText("FN-001"));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(updateTask).toHaveBeenCalledWith("KB-099", { dependencies: ["KB-001"] });
|
||||
expect(updateTask).toHaveBeenCalledWith("FN-099", { dependencies: ["FN-001"] });
|
||||
});
|
||||
});
|
||||
|
||||
@@ -511,7 +511,7 @@ describe("TaskDetailModal", () => {
|
||||
|
||||
render(
|
||||
<TaskDetailModal
|
||||
task={makeTask({ dependencies: ["KB-001", "KB-002"] })}
|
||||
task={makeTask({ dependencies: ["FN-001", "FN-002"] })}
|
||||
onClose={noop}
|
||||
onMoveTask={noopMove}
|
||||
onDeleteTask={noopDelete}
|
||||
@@ -525,7 +525,7 @@ describe("TaskDetailModal", () => {
|
||||
fireEvent.click(removeButtons[0]); // Remove KB-001
|
||||
|
||||
await waitFor(() => {
|
||||
expect(updateTask).toHaveBeenCalledWith("KB-099", { dependencies: ["KB-002"] });
|
||||
expect(updateTask).toHaveBeenCalledWith("FN-099", { dependencies: ["FN-002"] });
|
||||
});
|
||||
});
|
||||
|
||||
@@ -560,10 +560,10 @@ describe("TaskDetailModal", () => {
|
||||
|
||||
it("renders dependency dropdown items sorted newest-first by createdAt", () => {
|
||||
const allTasks: Task[] = [
|
||||
{ id: "KB-001", description: "Oldest", column: "todo" as Column, dependencies: [], steps: [], currentStep: 0, log: [], createdAt: "2026-01-01T00:00:00Z", updatedAt: "2026-01-01T00:00:00Z" },
|
||||
{ id: "KB-003", description: "Newest", column: "todo" as Column, dependencies: [], steps: [], currentStep: 0, log: [], createdAt: "2026-03-01T00:00:00Z", updatedAt: "2026-03-01T00:00:00Z" },
|
||||
{ id: "KB-002", description: "Middle", column: "todo" as Column, dependencies: [], steps: [], currentStep: 0, log: [], createdAt: "2026-02-01T00:00:00Z", updatedAt: "2026-02-01T00:00:00Z" },
|
||||
{ id: "KB-099", description: "Self", column: "in-progress" as Column, dependencies: [], steps: [], currentStep: 0, log: [], createdAt: "2026-03-15T00:00:00Z", updatedAt: "2026-03-15T00:00:00Z" },
|
||||
{ id: "FN-001", description: "Oldest", column: "todo" as Column, dependencies: [], steps: [], currentStep: 0, log: [], createdAt: "2026-01-01T00:00:00Z", updatedAt: "2026-01-01T00:00:00Z" },
|
||||
{ id: "FN-003", description: "Newest", column: "todo" as Column, dependencies: [], steps: [], currentStep: 0, log: [], createdAt: "2026-03-01T00:00:00Z", updatedAt: "2026-03-01T00:00:00Z" },
|
||||
{ id: "FN-002", description: "Middle", column: "todo" as Column, dependencies: [], steps: [], currentStep: 0, log: [], createdAt: "2026-02-01T00:00:00Z", updatedAt: "2026-02-01T00:00:00Z" },
|
||||
{ id: "FN-099", description: "Self", column: "in-progress" as Column, dependencies: [], steps: [], currentStep: 0, log: [], createdAt: "2026-03-15T00:00:00Z", updatedAt: "2026-03-15T00:00:00Z" },
|
||||
];
|
||||
|
||||
render(
|
||||
@@ -584,15 +584,15 @@ describe("TaskDetailModal", () => {
|
||||
expect(items).toHaveLength(3);
|
||||
|
||||
const ids = Array.from(items).map((el) => el.querySelector(".dep-dropdown-id")?.textContent);
|
||||
expect(ids).toEqual(["KB-003", "KB-002", "KB-001"]);
|
||||
expect(ids).toEqual(["FN-003", "FN-002", "FN-001"]);
|
||||
});
|
||||
|
||||
it("renders tasks with identical createdAt sorted newest-ID-first in dependency dropdown", () => {
|
||||
const allTasks: Task[] = [
|
||||
{ id: "KB-001", description: "First", column: "todo" as Column, dependencies: [], steps: [], currentStep: 0, log: [], createdAt: "2026-01-01T00:00:00Z", updatedAt: "2026-01-01T00:00:00Z" },
|
||||
{ id: "KB-002", description: "Second", column: "todo" as Column, dependencies: [], steps: [], currentStep: 0, log: [], createdAt: "2026-01-01T00:00:00Z", updatedAt: "2026-01-01T00:00:00Z" },
|
||||
{ id: "KB-003", description: "Third", column: "todo" as Column, dependencies: [], steps: [], currentStep: 0, log: [], createdAt: "2026-01-01T00:00:00Z", updatedAt: "2026-01-01T00:00:00Z" },
|
||||
{ id: "KB-099", description: "Self", column: "in-progress" as Column, dependencies: [], steps: [], currentStep: 0, log: [], createdAt: "2026-01-01T00:00:00Z", updatedAt: "2026-01-01T00:00:00Z" },
|
||||
{ id: "FN-001", description: "First", column: "todo" as Column, dependencies: [], steps: [], currentStep: 0, log: [], createdAt: "2026-01-01T00:00:00Z", updatedAt: "2026-01-01T00:00:00Z" },
|
||||
{ id: "FN-002", description: "Second", column: "todo" as Column, dependencies: [], steps: [], currentStep: 0, log: [], createdAt: "2026-01-01T00:00:00Z", updatedAt: "2026-01-01T00:00:00Z" },
|
||||
{ id: "FN-003", description: "Third", column: "todo" as Column, dependencies: [], steps: [], currentStep: 0, log: [], createdAt: "2026-01-01T00:00:00Z", updatedAt: "2026-01-01T00:00:00Z" },
|
||||
{ id: "FN-099", description: "Self", column: "in-progress" as Column, dependencies: [], steps: [], currentStep: 0, log: [], createdAt: "2026-01-01T00:00:00Z", updatedAt: "2026-01-01T00:00:00Z" },
|
||||
];
|
||||
|
||||
render(
|
||||
@@ -613,7 +613,7 @@ describe("TaskDetailModal", () => {
|
||||
expect(items).toHaveLength(3);
|
||||
|
||||
const ids = Array.from(items).map((el) => el.querySelector(".dep-dropdown-id")?.textContent);
|
||||
expect(ids).toEqual(["KB-003", "KB-002", "KB-001"]);
|
||||
expect(ids).toEqual(["FN-003", "FN-002", "FN-001"]);
|
||||
});
|
||||
|
||||
describe("tab toggle", () => {
|
||||
@@ -1258,7 +1258,7 @@ describe("TaskDetailModal", () => {
|
||||
number: 42,
|
||||
status: "open",
|
||||
title: "Task",
|
||||
headBranch: "kb/kb-099",
|
||||
headBranch: "fusion/fn-099",
|
||||
baseBranch: "main",
|
||||
commentCount: 0,
|
||||
} })}
|
||||
@@ -1297,10 +1297,10 @@ describe("TaskDetailModal", () => {
|
||||
|
||||
describe("dependency dropdown search", () => {
|
||||
const searchTasks: Task[] = [
|
||||
{ id: "KB-010", title: "Fix login bug", description: "Users cannot log in", column: "todo" as Column, dependencies: [], steps: [], currentStep: 0, log: [], createdAt: "2026-01-01T00:00:00Z", updatedAt: "2026-01-01T00:00:00Z" },
|
||||
{ id: "KB-020", title: "Add dark mode", description: "Theme support", column: "todo" as Column, dependencies: [], steps: [], currentStep: 0, log: [], createdAt: "2026-02-01T00:00:00Z", updatedAt: "2026-02-01T00:00:00Z" },
|
||||
{ id: "KB-030", title: "Refactor API", description: "Clean up endpoints", column: "todo" as Column, dependencies: [], steps: [], currentStep: 0, log: [], createdAt: "2026-03-01T00:00:00Z", updatedAt: "2026-03-01T00:00:00Z" },
|
||||
{ id: "KB-099", description: "Self", column: "in-progress" as Column, dependencies: [], steps: [], currentStep: 0, log: [], createdAt: "2026-03-15T00:00:00Z", updatedAt: "2026-03-15T00:00:00Z" },
|
||||
{ id: "FN-010", title: "Fix login bug", description: "Users cannot log in", column: "todo" as Column, dependencies: [], steps: [], currentStep: 0, log: [], createdAt: "2026-01-01T00:00:00Z", updatedAt: "2026-01-01T00:00:00Z" },
|
||||
{ id: "FN-020", title: "Add dark mode", description: "Theme support", column: "todo" as Column, dependencies: [], steps: [], currentStep: 0, log: [], createdAt: "2026-02-01T00:00:00Z", updatedAt: "2026-02-01T00:00:00Z" },
|
||||
{ id: "FN-030", title: "Refactor API", description: "Clean up endpoints", column: "todo" as Column, dependencies: [], steps: [], currentStep: 0, log: [], createdAt: "2026-03-01T00:00:00Z", updatedAt: "2026-03-01T00:00:00Z" },
|
||||
{ id: "FN-099", description: "Self", column: "in-progress" as Column, dependencies: [], steps: [], currentStep: 0, log: [], createdAt: "2026-03-15T00:00:00Z", updatedAt: "2026-03-15T00:00:00Z" },
|
||||
];
|
||||
|
||||
function renderWithSearch(taskOverrides: Partial<TaskDetail> = {}) {
|
||||
@@ -1334,7 +1334,7 @@ describe("TaskDetailModal", () => {
|
||||
|
||||
const items = document.querySelectorAll(".dep-dropdown-item");
|
||||
expect(items).toHaveLength(1);
|
||||
expect(items[0].querySelector(".dep-dropdown-id")?.textContent).toBe("KB-010");
|
||||
expect(items[0].querySelector(".dep-dropdown-id")?.textContent).toBe("FN-010");
|
||||
});
|
||||
|
||||
it("matches task ID case-insensitively", () => {
|
||||
@@ -1345,7 +1345,7 @@ describe("TaskDetailModal", () => {
|
||||
|
||||
const items = document.querySelectorAll(".dep-dropdown-item");
|
||||
expect(items).toHaveLength(1);
|
||||
expect(items[0].querySelector(".dep-dropdown-id")?.textContent).toBe("KB-020");
|
||||
expect(items[0].querySelector(".dep-dropdown-id")?.textContent).toBe("FN-020");
|
||||
});
|
||||
|
||||
it("matches task title", () => {
|
||||
@@ -1356,7 +1356,7 @@ describe("TaskDetailModal", () => {
|
||||
|
||||
const items = document.querySelectorAll(".dep-dropdown-item");
|
||||
expect(items).toHaveLength(1);
|
||||
expect(items[0].querySelector(".dep-dropdown-id")?.textContent).toBe("KB-020");
|
||||
expect(items[0].querySelector(".dep-dropdown-id")?.textContent).toBe("FN-020");
|
||||
});
|
||||
|
||||
it("shows empty state when search matches nothing", () => {
|
||||
@@ -1394,7 +1394,7 @@ describe("TaskDetailModal", () => {
|
||||
it("renders dependency list items with clickable class", () => {
|
||||
const { container } = render(
|
||||
<TaskDetailModal
|
||||
task={makeTask({ dependencies: ["KB-001", "KB-002"] })}
|
||||
task={makeTask({ dependencies: ["FN-001", "FN-002"] })}
|
||||
onClose={noop}
|
||||
onMoveTask={noopMove}
|
||||
onDeleteTask={noopDelete}
|
||||
@@ -1406,15 +1406,15 @@ describe("TaskDetailModal", () => {
|
||||
|
||||
const depLinks = container.querySelectorAll(".detail-dep-link");
|
||||
expect(depLinks).toHaveLength(2);
|
||||
expect(depLinks[0].textContent).toBe("KB-001");
|
||||
expect(depLinks[1].textContent).toBe("KB-002");
|
||||
expect(depLinks[0].textContent).toBe("FN-001");
|
||||
expect(depLinks[1].textContent).toBe("FN-002");
|
||||
});
|
||||
|
||||
it("calls fetchTaskDetail and onOpenDetail when clicking a dependency", async () => {
|
||||
const { fetchTaskDetail } = await import("../../api");
|
||||
const mockFetch = vi.mocked(fetchTaskDetail);
|
||||
const mockDetail: TaskDetail = {
|
||||
...makeTask({ id: "KB-001", description: "Dep 1" }),
|
||||
...makeTask({ id: "FN-001", description: "Dep 1" }),
|
||||
prompt: "",
|
||||
attachments: [],
|
||||
};
|
||||
@@ -1423,7 +1423,7 @@ describe("TaskDetailModal", () => {
|
||||
|
||||
const { container } = render(
|
||||
<TaskDetailModal
|
||||
task={makeTask({ dependencies: ["KB-001"] })}
|
||||
task={makeTask({ dependencies: ["FN-001"] })}
|
||||
onOpenDetail={onOpenDetail}
|
||||
onClose={noop}
|
||||
onMoveTask={noopMove}
|
||||
@@ -1437,7 +1437,7 @@ describe("TaskDetailModal", () => {
|
||||
fireEvent.click(depLink);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockFetch).toHaveBeenCalledWith("KB-001");
|
||||
expect(mockFetch).toHaveBeenCalledWith("FN-001");
|
||||
expect(onOpenDetail).toHaveBeenCalledWith(mockDetail);
|
||||
});
|
||||
});
|
||||
@@ -1451,7 +1451,7 @@ describe("TaskDetailModal", () => {
|
||||
|
||||
const { container } = render(
|
||||
<TaskDetailModal
|
||||
task={makeTask({ dependencies: ["KB-001"] })}
|
||||
task={makeTask({ dependencies: ["FN-001"] })}
|
||||
onOpenDetail={onOpenDetail}
|
||||
onClose={noop}
|
||||
onMoveTask={noopMove}
|
||||
@@ -1479,7 +1479,7 @@ describe("TaskDetailModal", () => {
|
||||
|
||||
render(
|
||||
<TaskDetailModal
|
||||
task={makeTask({ dependencies: ["KB-001"] })}
|
||||
task={makeTask({ dependencies: ["FN-001"] })}
|
||||
onOpenDetail={onOpenDetail}
|
||||
onClose={noop}
|
||||
onMoveTask={noopMove}
|
||||
@@ -1496,7 +1496,7 @@ describe("TaskDetailModal", () => {
|
||||
expect(onOpenDetail).not.toHaveBeenCalled();
|
||||
// updateTask should be called to remove the dependency
|
||||
await waitFor(() => {
|
||||
expect(updateTask).toHaveBeenCalledWith("KB-099", { dependencies: [] });
|
||||
expect(updateTask).toHaveBeenCalledWith("FN-099", { dependencies: [] });
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1571,11 +1571,11 @@ describe("TaskDetailModal", () => {
|
||||
it("saving updates the task and returns to view mode", async () => {
|
||||
const { updateTask } = await import("../../api");
|
||||
const mockUpdate = vi.mocked(updateTask);
|
||||
mockUpdate.mockResolvedValueOnce({ id: "KB-099" } as Task);
|
||||
mockUpdate.mockResolvedValueOnce({ id: "FN-099" } as Task);
|
||||
|
||||
const { container } = render(
|
||||
<TaskDetailModal
|
||||
task={makeTask({ id: "KB-099", prompt: "# Original" })}
|
||||
task={makeTask({ id: "FN-099", prompt: "# Original" })}
|
||||
onClose={noop}
|
||||
onMoveTask={noopMove}
|
||||
onDeleteTask={noopDelete}
|
||||
@@ -1592,7 +1592,7 @@ describe("TaskDetailModal", () => {
|
||||
fireEvent.click(screen.getByText("Save"));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockUpdate).toHaveBeenCalledWith("KB-099", { prompt: "# Updated" });
|
||||
expect(mockUpdate).toHaveBeenCalledWith("FN-099", { prompt: "# Updated" });
|
||||
});
|
||||
|
||||
// Should return to view mode
|
||||
@@ -1627,7 +1627,7 @@ describe("TaskDetailModal", () => {
|
||||
|
||||
render(
|
||||
<TaskDetailModal
|
||||
task={makeTask({ id: "KB-099", column: "todo", prompt: "# Test" })}
|
||||
task={makeTask({ id: "FN-099", column: "todo", prompt: "# Test" })}
|
||||
onClose={onClose}
|
||||
onMoveTask={noopMove}
|
||||
onDeleteTask={noopDelete}
|
||||
@@ -1645,7 +1645,7 @@ describe("TaskDetailModal", () => {
|
||||
fireEvent.click(screen.getByText("Request AI Revision"));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(requestSpecRevision).toHaveBeenCalledWith("KB-099", "Please add more error handling details");
|
||||
expect(requestSpecRevision).toHaveBeenCalledWith("FN-099", "Please add more error handling details");
|
||||
expect(addToast).toHaveBeenCalledWith("AI revision requested. Task moved to triage.", "success");
|
||||
expect(onClose).toHaveBeenCalled();
|
||||
});
|
||||
@@ -1786,7 +1786,7 @@ describe("TaskDetailModal", () => {
|
||||
render(
|
||||
<TaskDetailModal
|
||||
task={makeTask({
|
||||
id: "KB-001",
|
||||
id: "FN-001",
|
||||
column: "triage",
|
||||
status: "awaiting-approval",
|
||||
prompt: "# Task Spec",
|
||||
@@ -1803,7 +1803,7 @@ describe("TaskDetailModal", () => {
|
||||
fireEvent.click(screen.getByText("Approve Plan"));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockApprovePlan).toHaveBeenCalledWith("KB-001");
|
||||
expect(mockApprovePlan).toHaveBeenCalledWith("FN-001");
|
||||
});
|
||||
expect(addToast).toHaveBeenCalledWith("Plan approved — KB-001 moved to Todo", "success");
|
||||
expect(onClose).toHaveBeenCalled();
|
||||
@@ -1822,7 +1822,7 @@ describe("TaskDetailModal", () => {
|
||||
render(
|
||||
<TaskDetailModal
|
||||
task={makeTask({
|
||||
id: "KB-001",
|
||||
id: "FN-001",
|
||||
column: "triage",
|
||||
status: "awaiting-approval",
|
||||
prompt: "# Task Spec",
|
||||
@@ -1843,7 +1843,7 @@ describe("TaskDetailModal", () => {
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockRejectPlan).toHaveBeenCalledWith("KB-001");
|
||||
expect(mockRejectPlan).toHaveBeenCalledWith("FN-001");
|
||||
});
|
||||
expect(addToast).toHaveBeenCalledWith(
|
||||
"Plan rejected — KB-001 returned to Triage for re-specification",
|
||||
@@ -1900,7 +1900,7 @@ describe("TaskDetailModal", () => {
|
||||
render(
|
||||
<TaskDetailModal
|
||||
task={makeTask({
|
||||
id: "KB-001",
|
||||
id: "FN-001",
|
||||
column: "triage",
|
||||
status: "awaiting-approval",
|
||||
prompt: "# Task Spec",
|
||||
@@ -1935,7 +1935,7 @@ describe("TaskDetailModal", () => {
|
||||
render(
|
||||
<TaskDetailModal
|
||||
task={makeTask({
|
||||
id: "KB-001",
|
||||
id: "FN-001",
|
||||
column: "triage",
|
||||
status: "awaiting-approval",
|
||||
prompt: "# Task Spec",
|
||||
@@ -1999,7 +1999,7 @@ describe("TaskDetailModal", () => {
|
||||
|
||||
render(
|
||||
<TaskDetailModal
|
||||
task={makeTask({ id: "KB-001" })}
|
||||
task={makeTask({ id: "FN-001" })}
|
||||
onClose={noop}
|
||||
onMoveTask={noopMove}
|
||||
onDeleteTask={noopDelete}
|
||||
@@ -2023,12 +2023,12 @@ describe("TaskDetailModal", () => {
|
||||
const originalConfirm = window.confirm;
|
||||
window.confirm = vi.fn(() => true);
|
||||
|
||||
const mockDuplicate = vi.fn().mockResolvedValue({ id: "KB-002" } as Task);
|
||||
const mockDuplicate = vi.fn().mockResolvedValue({ id: "FN-002" } as Task);
|
||||
const onClose = vi.fn();
|
||||
|
||||
render(
|
||||
<TaskDetailModal
|
||||
task={makeTask({ id: "KB-001" })}
|
||||
task={makeTask({ id: "FN-001" })}
|
||||
onClose={onClose}
|
||||
onMoveTask={noopMove}
|
||||
onDeleteTask={noopDelete}
|
||||
@@ -2042,7 +2042,7 @@ describe("TaskDetailModal", () => {
|
||||
fireEvent.click(screen.getByText("Duplicate"));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockDuplicate).toHaveBeenCalledWith("KB-001");
|
||||
expect(mockDuplicate).toHaveBeenCalledWith("FN-001");
|
||||
expect(onClose).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
@@ -2053,12 +2053,12 @@ describe("TaskDetailModal", () => {
|
||||
const originalConfirm = window.confirm;
|
||||
window.confirm = vi.fn(() => true);
|
||||
|
||||
const mockDuplicate = vi.fn().mockResolvedValue({ id: "KB-002" } as Task);
|
||||
const mockDuplicate = vi.fn().mockResolvedValue({ id: "FN-002" } as Task);
|
||||
const addToast = vi.fn();
|
||||
|
||||
render(
|
||||
<TaskDetailModal
|
||||
task={makeTask({ id: "KB-001" })}
|
||||
task={makeTask({ id: "FN-001" })}
|
||||
onClose={noop}
|
||||
onMoveTask={noopMove}
|
||||
onDeleteTask={noopDelete}
|
||||
@@ -2082,11 +2082,11 @@ describe("TaskDetailModal", () => {
|
||||
const originalConfirm = window.confirm;
|
||||
window.confirm = vi.fn(() => false);
|
||||
|
||||
const mockDuplicate = vi.fn().mockResolvedValue({ id: "KB-002" } as Task);
|
||||
const mockDuplicate = vi.fn().mockResolvedValue({ id: "FN-002" } as Task);
|
||||
|
||||
render(
|
||||
<TaskDetailModal
|
||||
task={makeTask({ id: "KB-001" })}
|
||||
task={makeTask({ id: "FN-001" })}
|
||||
onClose={noop}
|
||||
onMoveTask={noopMove}
|
||||
onDeleteTask={noopDelete}
|
||||
@@ -2113,7 +2113,7 @@ describe("TaskDetailModal", () => {
|
||||
|
||||
render(
|
||||
<TaskDetailModal
|
||||
task={makeTask({ id: "KB-001" })}
|
||||
task={makeTask({ id: "FN-001" })}
|
||||
onClose={noop}
|
||||
onMoveTask={noopMove}
|
||||
onDeleteTask={noopDelete}
|
||||
@@ -2218,7 +2218,7 @@ describe("TaskDetailModal", () => {
|
||||
it("clicking Refine opens the refinement modal", () => {
|
||||
render(
|
||||
<TaskDetailModal
|
||||
task={makeTask({ id: "KB-001", column: "done" })}
|
||||
task={makeTask({ id: "FN-001", column: "done" })}
|
||||
onClose={noop}
|
||||
onMoveTask={noopMove}
|
||||
onDeleteTask={noopDelete}
|
||||
@@ -2237,7 +2237,7 @@ describe("TaskDetailModal", () => {
|
||||
it("shows character counter in refinement modal", () => {
|
||||
render(
|
||||
<TaskDetailModal
|
||||
task={makeTask({ id: "KB-001", column: "done" })}
|
||||
task={makeTask({ id: "FN-001", column: "done" })}
|
||||
onClose={noop}
|
||||
onMoveTask={noopMove}
|
||||
onDeleteTask={noopDelete}
|
||||
@@ -2255,7 +2255,7 @@ describe("TaskDetailModal", () => {
|
||||
it("character counter updates when typing feedback", async () => {
|
||||
render(
|
||||
<TaskDetailModal
|
||||
task={makeTask({ id: "KB-001", column: "done" })}
|
||||
task={makeTask({ id: "FN-001", column: "done" })}
|
||||
onClose={noop}
|
||||
onMoveTask={noopMove}
|
||||
onDeleteTask={noopDelete}
|
||||
@@ -2278,7 +2278,7 @@ describe("TaskDetailModal", () => {
|
||||
it("submit button is disabled when feedback is empty", () => {
|
||||
render(
|
||||
<TaskDetailModal
|
||||
task={makeTask({ id: "KB-001", column: "done" })}
|
||||
task={makeTask({ id: "FN-001", column: "done" })}
|
||||
onClose={noop}
|
||||
onMoveTask={noopMove}
|
||||
onDeleteTask={noopDelete}
|
||||
@@ -2297,7 +2297,7 @@ describe("TaskDetailModal", () => {
|
||||
it("submit button is enabled when feedback is entered", async () => {
|
||||
render(
|
||||
<TaskDetailModal
|
||||
task={makeTask({ id: "KB-001", column: "done" })}
|
||||
task={makeTask({ id: "FN-001", column: "done" })}
|
||||
onClose={noop}
|
||||
onMoveTask={noopMove}
|
||||
onDeleteTask={noopDelete}
|
||||
@@ -2323,7 +2323,7 @@ describe("TaskDetailModal", () => {
|
||||
|
||||
render(
|
||||
<TaskDetailModal
|
||||
task={makeTask({ id: "KB-001", column: "done" })}
|
||||
task={makeTask({ id: "FN-001", column: "done" })}
|
||||
onClose={onClose}
|
||||
onMoveTask={noopMove}
|
||||
onDeleteTask={noopDelete}
|
||||
@@ -2346,7 +2346,7 @@ describe("TaskDetailModal", () => {
|
||||
|
||||
render(
|
||||
<TaskDetailModal
|
||||
task={makeTask({ id: "KB-001", column: "done" })}
|
||||
task={makeTask({ id: "FN-001", column: "done" })}
|
||||
onClose={noop}
|
||||
onMoveTask={noopMove}
|
||||
onDeleteTask={noopDelete}
|
||||
@@ -2367,14 +2367,14 @@ describe("TaskDetailModal", () => {
|
||||
|
||||
it("calls refineTask and closes modal on successful submission", async () => {
|
||||
const { refineTask } = await import("../../api");
|
||||
vi.mocked(refineTask).mockResolvedValue({ id: "KB-002", column: "triage" } as Task);
|
||||
vi.mocked(refineTask).mockResolvedValue({ id: "FN-002", column: "triage" } as Task);
|
||||
|
||||
const onClose = vi.fn();
|
||||
const addToast = vi.fn();
|
||||
|
||||
render(
|
||||
<TaskDetailModal
|
||||
task={makeTask({ id: "KB-001", column: "done" })}
|
||||
task={makeTask({ id: "FN-001", column: "done" })}
|
||||
onClose={onClose}
|
||||
onMoveTask={noopMove}
|
||||
onDeleteTask={noopDelete}
|
||||
@@ -2392,7 +2392,7 @@ describe("TaskDetailModal", () => {
|
||||
fireEvent.click(screen.getByText("Create Refinement Task"));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(refineTask).toHaveBeenCalledWith("KB-001", "Need to add more tests");
|
||||
expect(refineTask).toHaveBeenCalledWith("FN-001", "Need to add more tests");
|
||||
expect(addToast).toHaveBeenCalledWith("Refinement task created: KB-002", "success");
|
||||
expect(onClose).toHaveBeenCalled();
|
||||
});
|
||||
@@ -2406,7 +2406,7 @@ describe("TaskDetailModal", () => {
|
||||
|
||||
render(
|
||||
<TaskDetailModal
|
||||
task={makeTask({ id: "KB-001", column: "done" })}
|
||||
task={makeTask({ id: "FN-001", column: "done" })}
|
||||
onClose={noop}
|
||||
onMoveTask={noopMove}
|
||||
onDeleteTask={noopDelete}
|
||||
@@ -2437,7 +2437,7 @@ describe("TaskDetailModal", () => {
|
||||
it("shows Edit button in header when task is in triage column", () => {
|
||||
const { container } = render(
|
||||
<TaskDetailModal
|
||||
task={makeTask({ id: "KB-001", column: "triage", title: "Test task" })}
|
||||
task={makeTask({ id: "FN-001", column: "triage", title: "Test task" })}
|
||||
onClose={noop}
|
||||
onMoveTask={noopMove}
|
||||
onDeleteTask={noopDelete}
|
||||
@@ -2454,7 +2454,7 @@ describe("TaskDetailModal", () => {
|
||||
it("shows Edit button in header when task is in todo column", () => {
|
||||
const { container } = render(
|
||||
<TaskDetailModal
|
||||
task={makeTask({ id: "KB-001", column: "todo", title: "Test task" })}
|
||||
task={makeTask({ id: "FN-001", column: "todo", title: "Test task" })}
|
||||
onClose={noop}
|
||||
onMoveTask={noopMove}
|
||||
onDeleteTask={noopDelete}
|
||||
@@ -2471,7 +2471,7 @@ describe("TaskDetailModal", () => {
|
||||
it("does not show Edit button when task is in in-progress column", () => {
|
||||
const { container } = render(
|
||||
<TaskDetailModal
|
||||
task={makeTask({ id: "KB-001", column: "in-progress", title: "Test task" })}
|
||||
task={makeTask({ id: "FN-001", column: "in-progress", title: "Test task" })}
|
||||
onClose={noop}
|
||||
onMoveTask={noopMove}
|
||||
onDeleteTask={noopDelete}
|
||||
@@ -2488,7 +2488,7 @@ describe("TaskDetailModal", () => {
|
||||
it("does not show Edit button when already in edit mode", () => {
|
||||
const { container } = render(
|
||||
<TaskDetailModal
|
||||
task={makeTask({ id: "KB-001", column: "triage", title: "Test task" })}
|
||||
task={makeTask({ id: "FN-001", column: "triage", title: "Test task" })}
|
||||
onClose={noop}
|
||||
onMoveTask={noopMove}
|
||||
onDeleteTask={noopDelete}
|
||||
@@ -2512,7 +2512,7 @@ describe("TaskDetailModal", () => {
|
||||
it("entering edit mode shows title input and description textarea", () => {
|
||||
const { container } = render(
|
||||
<TaskDetailModal
|
||||
task={makeTask({ id: "KB-001", column: "triage", title: "Test task", description: "Test description" })}
|
||||
task={makeTask({ id: "FN-001", column: "triage", title: "Test task", description: "Test description" })}
|
||||
onClose={noop}
|
||||
onMoveTask={noopMove}
|
||||
onDeleteTask={noopDelete}
|
||||
@@ -2538,7 +2538,7 @@ describe("TaskDetailModal", () => {
|
||||
it("clicking Cancel exits edit mode without saving", () => {
|
||||
const { container } = render(
|
||||
<TaskDetailModal
|
||||
task={makeTask({ id: "KB-001", column: "triage", title: "Original title", description: "Original description" })}
|
||||
task={makeTask({ id: "FN-001", column: "triage", title: "Original title", description: "Original description" })}
|
||||
onClose={noop}
|
||||
onMoveTask={noopMove}
|
||||
onDeleteTask={noopDelete}
|
||||
@@ -2566,11 +2566,11 @@ describe("TaskDetailModal", () => {
|
||||
it("clicking Save calls updateTask with correct parameters", async () => {
|
||||
const { updateTask } = await import("../../api");
|
||||
const mockUpdate = vi.mocked(updateTask);
|
||||
mockUpdate.mockResolvedValueOnce({ id: "KB-001" } as Task);
|
||||
mockUpdate.mockResolvedValueOnce({ id: "FN-001" } as Task);
|
||||
|
||||
const { container } = render(
|
||||
<TaskDetailModal
|
||||
task={makeTask({ id: "KB-001", column: "triage", title: "Original title", description: "Original description" })}
|
||||
task={makeTask({ id: "FN-001", column: "triage", title: "Original title", description: "Original description" })}
|
||||
onClose={noop}
|
||||
onMoveTask={noopMove}
|
||||
onDeleteTask={noopDelete}
|
||||
@@ -2593,7 +2593,7 @@ describe("TaskDetailModal", () => {
|
||||
fireEvent.click(screen.getByText("Save"));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockUpdate).toHaveBeenCalledWith("KB-001", {
|
||||
expect(mockUpdate).toHaveBeenCalledWith("FN-001", {
|
||||
title: "New title",
|
||||
description: "New description",
|
||||
});
|
||||
@@ -2603,7 +2603,7 @@ describe("TaskDetailModal", () => {
|
||||
it("Save button is disabled when no changes made", () => {
|
||||
const { container } = render(
|
||||
<TaskDetailModal
|
||||
task={makeTask({ id: "KB-001", column: "triage", title: "Test title", description: "Test description" })}
|
||||
task={makeTask({ id: "FN-001", column: "triage", title: "Test title", description: "Test description" })}
|
||||
onClose={noop}
|
||||
onMoveTask={noopMove}
|
||||
onDeleteTask={noopDelete}
|
||||
@@ -2624,11 +2624,11 @@ describe("TaskDetailModal", () => {
|
||||
const { updateTask } = await import("../../api");
|
||||
const mockUpdate = vi.mocked(updateTask);
|
||||
// Delay the resolution to keep isSaving true
|
||||
mockUpdate.mockImplementationOnce(() => new Promise(resolve => setTimeout(() => resolve({ id: "KB-001" } as Task), 100)));
|
||||
mockUpdate.mockImplementationOnce(() => new Promise(resolve => setTimeout(() => resolve({ id: "FN-001" } as Task), 100)));
|
||||
|
||||
const { container } = render(
|
||||
<TaskDetailModal
|
||||
task={makeTask({ id: "KB-001", column: "triage", title: "Original" })}
|
||||
task={makeTask({ id: "FN-001", column: "triage", title: "Original" })}
|
||||
onClose={noop}
|
||||
onMoveTask={noopMove}
|
||||
onDeleteTask={noopDelete}
|
||||
@@ -2655,13 +2655,13 @@ describe("TaskDetailModal", () => {
|
||||
it("successful save shows toast and exits edit mode", async () => {
|
||||
const { updateTask } = await import("../../api");
|
||||
const mockUpdate = vi.mocked(updateTask);
|
||||
mockUpdate.mockResolvedValueOnce({ id: "KB-001" } as Task);
|
||||
mockUpdate.mockResolvedValueOnce({ id: "FN-001" } as Task);
|
||||
|
||||
const addToast = vi.fn();
|
||||
|
||||
const { container } = render(
|
||||
<TaskDetailModal
|
||||
task={makeTask({ id: "KB-001", column: "triage", title: "Original" })}
|
||||
task={makeTask({ id: "FN-001", column: "triage", title: "Original" })}
|
||||
onClose={noop}
|
||||
onMoveTask={noopMove}
|
||||
onDeleteTask={noopDelete}
|
||||
@@ -2698,7 +2698,7 @@ describe("TaskDetailModal", () => {
|
||||
|
||||
const { container } = render(
|
||||
<TaskDetailModal
|
||||
task={makeTask({ id: "KB-001", column: "triage", title: "Original" })}
|
||||
task={makeTask({ id: "FN-001", column: "triage", title: "Original" })}
|
||||
onClose={noop}
|
||||
onMoveTask={noopMove}
|
||||
onDeleteTask={noopDelete}
|
||||
@@ -2719,7 +2719,7 @@ describe("TaskDetailModal", () => {
|
||||
fireEvent.click(screen.getByText("Save"));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(addToast).toHaveBeenCalledWith("Failed to update KB-001: Network error", "error");
|
||||
expect(addToast).toHaveBeenCalledWith("Failed to update FN-001: Network error", "error");
|
||||
});
|
||||
|
||||
// Should stay in edit mode
|
||||
@@ -2729,7 +2729,7 @@ describe("TaskDetailModal", () => {
|
||||
it("Escape key exits edit mode", () => {
|
||||
const { container } = render(
|
||||
<TaskDetailModal
|
||||
task={makeTask({ id: "KB-001", column: "triage", title: "Test title" })}
|
||||
task={makeTask({ id: "FN-001", column: "triage", title: "Test title" })}
|
||||
onClose={noop}
|
||||
onMoveTask={noopMove}
|
||||
onDeleteTask={noopDelete}
|
||||
@@ -2754,7 +2754,7 @@ describe("TaskDetailModal", () => {
|
||||
it("Enter in title input moves focus to description textarea", () => {
|
||||
const { container } = render(
|
||||
<TaskDetailModal
|
||||
task={makeTask({ id: "KB-001", column: "triage", title: "Test title", description: "Test description" })}
|
||||
task={makeTask({ id: "FN-001", column: "triage", title: "Test title", description: "Test description" })}
|
||||
onClose={noop}
|
||||
onMoveTask={noopMove}
|
||||
onDeleteTask={noopDelete}
|
||||
|
||||
@@ -6,15 +6,15 @@ import type { WorkspaceInfo } from "../../hooks/useWorkspaces";
|
||||
|
||||
const workspaces: WorkspaceInfo[] = [
|
||||
{
|
||||
id: "KB-123",
|
||||
label: "KB-123",
|
||||
id: "FN-123",
|
||||
label: "FN-123",
|
||||
title: "Implement a very long task title that should be truncated in the dropdown list",
|
||||
worktree: "/repo/.worktrees/kb-123",
|
||||
kind: "task",
|
||||
},
|
||||
{
|
||||
id: "KB-200",
|
||||
label: "KB-200",
|
||||
id: "FN-200",
|
||||
label: "FN-200",
|
||||
title: "Short title",
|
||||
worktree: "/repo/.worktrees/kb-200",
|
||||
kind: "task",
|
||||
@@ -39,7 +39,7 @@ describe("WorkspaceSelector", () => {
|
||||
const user = userEvent.setup();
|
||||
render(
|
||||
<WorkspaceSelector
|
||||
currentWorkspace="KB-200"
|
||||
currentWorkspace="FN-200"
|
||||
projectName="kb"
|
||||
workspaces={workspaces}
|
||||
onSelect={vi.fn()}
|
||||
@@ -69,7 +69,7 @@ describe("WorkspaceSelector", () => {
|
||||
await user.click(screen.getByRole("button", { name: /kb/i }));
|
||||
await user.click(screen.getByRole("button", { name: /KB-123/i }));
|
||||
|
||||
expect(onSelect).toHaveBeenCalledWith("KB-123");
|
||||
expect(onSelect).toHaveBeenCalledWith("FN-123");
|
||||
expect(screen.queryByText("Task Worktrees")).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user