import { describe, expect, it } from "vitest"; import { Task } from "@/model/task"; import { TASK_STATUS_META, TASK_STATUS_ORDER, TaskStatus, } from "@/model/taskStatus"; import { buildKanbanBoard } from "./buildKanbanBoard"; function task(partial: Partial & Pick): Task { return { description: "", status: TaskStatus.TODO, priority: "medium", tags: [], assignee: "", created: "2026-01-01T00:00:00.000Z", updated: "2026-01-01T00:00:00.000Z", ...partial, }; } const visibleStatuses = TASK_STATUS_ORDER.filter( (status) => !TASK_STATUS_META[status].defaultHidden, ); describe("buildKanbanBoard", () => { const tasks: Task[] = [ task({ id: "a", title: "Done one", status: TaskStatus.DONE, updated: "2026-01-01T00:00:02.000Z", }), task({ id: "b", title: "Todo older", status: TaskStatus.TODO, updated: "2026-01-01T00:00:01.000Z", }), task({ id: "c", title: "Todo newer", status: TaskStatus.TODO, updated: "2026-01-01T00:00:03.000Z", }), task({ id: "d", title: "In progress", status: TaskStatus.IN_PROGRESS, updated: "2026-01-01T00:00:04.000Z", }), task({ id: "e", title: "Backlog item", status: TaskStatus.BACKLOG, updated: "2026-01-01T00:00:00.000Z", }), task({ id: "f", title: "Cancelled", status: TaskStatus.CANCELLED, updated: "2026-01-01T00:00:05.000Z", }), ]; it("hides defaultHidden statuses by default", () => { const board = buildKanbanBoard(tasks); const statuses = board.columns.map((col) => col.status); expect(statuses).toEqual(visibleStatuses); for (const col of board.columns) { expect(TASK_STATUS_META[col.status].defaultHidden).toBe(false); expect(col.label).toBe(TASK_STATUS_META[col.status].label); } }); it("includes all statuses when showHiddenStatuses is true", () => { const board = buildKanbanBoard(tasks, { showHiddenStatuses: true }); expect(board.columns.map((col) => col.status)).toEqual(TASK_STATUS_ORDER); for (const col of board.columns) { expect(col.label).toBe(TASK_STATUS_META[col.status].label); } }); it("places each task into the column matching its status", () => { const board = buildKanbanBoard(tasks, { showHiddenStatuses: true }); for (const col of board.columns) { for (const t of col.tasks) { expect(t.status).toBe(col.status); } } const byStatus = Object.fromEntries( board.columns.map((col) => [col.status, col.tasks.map((t) => t.id)]), ); expect(byStatus[TaskStatus.TODO]).toEqual(["c", "b"]); expect(byStatus[TaskStatus.IN_PROGRESS]).toEqual(["d"]); expect(byStatus[TaskStatus.DONE]).toEqual(["a"]); expect(byStatus[TaskStatus.BACKLOG]).toEqual(["e"]); expect(byStatus[TaskStatus.CANCELLED]).toEqual(["f"]); }); it("sorts tasks within a column by updated desc", () => { const board = buildKanbanBoard(tasks); const todo = board.columns.find((col) => col.status === TaskStatus.TODO); expect(todo?.tasks.map((t) => t.id)).toEqual(["c", "b"]); }); it("returns empty visible columns when there are no tasks", () => { const board = buildKanbanBoard([]); expect(board.columns.map((col) => col.status)).toEqual(visibleStatuses); for (const col of board.columns) { expect(col.tasks).toEqual([]); } }); it("preserves input order for tasks with same updated timestamp (stable sort)", () => { const sameUpdated = "2026-01-01T00:00:00.000Z"; const tasks: Task[] = [ task({ id: "a", title: "A", status: TaskStatus.TODO, updated: sameUpdated, }), task({ id: "b", title: "B", status: TaskStatus.TODO, updated: sameUpdated, }), task({ id: "c", title: "C", status: TaskStatus.TODO, updated: sameUpdated, }), ]; const board = buildKanbanBoard(tasks); const todo = board.columns.find((col) => col.status === TaskStatus.TODO); expect(todo?.tasks.map((t) => t.id)).toEqual(["a", "b", "c"]); }); });