feat(KB-069): add Archive All Done feature
- Add archiveAllDone method to TaskStore with filtering and batch archive - Add POST /tasks/archive-all-done API endpoint with tests - Add useTasks hook support and API function for archiveAllDone - Add Archive All button to done column header in Board UI - Wire up onArchiveAllDone through Board component hierarchy
This commit is contained in:
@@ -2113,6 +2113,96 @@ describe("TaskStore", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("archiveAllDone", () => {
|
||||
it("archives multiple done tasks", async () => {
|
||||
const task1 = await store.createTask({ description: "Test task 1" });
|
||||
const task2 = await store.createTask({ description: "Test task 2" });
|
||||
const task3 = await store.createTask({ description: "Test task 3" });
|
||||
|
||||
// Move all to done
|
||||
for (const task of [task1, task2, task3]) {
|
||||
await store.moveTask(task.id, "todo");
|
||||
await store.moveTask(task.id, "in-progress");
|
||||
await store.moveTask(task.id, "in-review");
|
||||
await store.moveTask(task.id, "done");
|
||||
}
|
||||
|
||||
const archived = await store.archiveAllDone();
|
||||
|
||||
expect(archived).toHaveLength(3);
|
||||
expect(archived.every((t) => t.column === "archived")).toBe(true);
|
||||
});
|
||||
|
||||
it("returns empty array when no done tasks exist", async () => {
|
||||
const result = await store.archiveAllDone();
|
||||
|
||||
expect(result).toEqual([]);
|
||||
});
|
||||
|
||||
it("emits task:moved event for each archived task", async () => {
|
||||
const task1 = await store.createTask({ description: "Test task 1" });
|
||||
const task2 = await store.createTask({ description: "Test task 2" });
|
||||
|
||||
for (const task of [task1, task2]) {
|
||||
await store.moveTask(task.id, "todo");
|
||||
await store.moveTask(task.id, "in-progress");
|
||||
await store.moveTask(task.id, "in-review");
|
||||
await store.moveTask(task.id, "done");
|
||||
}
|
||||
|
||||
const events: any[] = [];
|
||||
store.on("task:moved", (data) => events.push(data));
|
||||
|
||||
await store.archiveAllDone();
|
||||
|
||||
expect(events).toHaveLength(2);
|
||||
expect(events.every((e) => e.from === "done" && e.to === "archived")).toBe(true);
|
||||
});
|
||||
|
||||
it("does not affect tasks in other columns", async () => {
|
||||
const doneTask = await store.createTask({ description: "Done task" });
|
||||
await store.moveTask(doneTask.id, "todo");
|
||||
await store.moveTask(doneTask.id, "in-progress");
|
||||
await store.moveTask(doneTask.id, "in-review");
|
||||
await store.moveTask(doneTask.id, "done");
|
||||
|
||||
const todoTask = await store.createTask({ description: "Todo task" });
|
||||
await store.moveTask(todoTask.id, "todo");
|
||||
|
||||
const inProgressTask = await store.createTask({ description: "In progress task" });
|
||||
await store.moveTask(inProgressTask.id, "todo");
|
||||
await store.moveTask(inProgressTask.id, "in-progress");
|
||||
|
||||
await store.archiveAllDone();
|
||||
|
||||
const fetchedTodo = await store.getTask(todoTask.id);
|
||||
const fetchedInProgress = await store.getTask(inProgressTask.id);
|
||||
|
||||
expect(fetchedTodo.column).toBe("todo");
|
||||
expect(fetchedInProgress.column).toBe("in-progress");
|
||||
});
|
||||
|
||||
it("archives only done tasks when mixed columns exist", async () => {
|
||||
const doneTask1 = await store.createTask({ description: "Done task 1" });
|
||||
const doneTask2 = await store.createTask({ description: "Done task 2" });
|
||||
const todoTask = await store.createTask({ description: "Todo task" });
|
||||
|
||||
for (const task of [doneTask1, doneTask2]) {
|
||||
await store.moveTask(task.id, "todo");
|
||||
await store.moveTask(task.id, "in-progress");
|
||||
await store.moveTask(task.id, "in-review");
|
||||
await store.moveTask(task.id, "done");
|
||||
}
|
||||
|
||||
await store.moveTask(todoTask.id, "todo");
|
||||
|
||||
const archived = await store.archiveAllDone();
|
||||
|
||||
expect(archived).toHaveLength(2);
|
||||
expect(archived.map((t) => t.id).sort()).toEqual([doneTask1.id, doneTask2.id].sort());
|
||||
});
|
||||
});
|
||||
|
||||
describe("VALID_TRANSITIONS — invalid archived transitions via moveTask", () => {
|
||||
it("moveTask from archived → in-progress should fail", async () => {
|
||||
const task = await store.createTask({ description: "Test task" });
|
||||
|
||||
@@ -875,6 +875,26 @@ export class TaskStore extends EventEmitter<TaskStoreEvents> {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Archive all tasks currently in the "done" column.
|
||||
* Returns an array of archived tasks.
|
||||
*/
|
||||
async archiveAllDone(): Promise<Task[]> {
|
||||
const tasks = await this.listTasks();
|
||||
const doneTasks = tasks.filter((t) => t.column === "done");
|
||||
|
||||
if (doneTasks.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Archive all done tasks concurrently
|
||||
const archivedTasks = await Promise.all(
|
||||
doneTasks.map((task) => this.archiveTask(task.id))
|
||||
);
|
||||
|
||||
return archivedTasks;
|
||||
}
|
||||
|
||||
/**
|
||||
* Archive a done task (move from done → archived).
|
||||
* Logs the action and emits `task:moved` event.
|
||||
|
||||
Reference in New Issue
Block a user