fix(tasktree): project lifecycle times into timelines
This commit is contained in:
@@ -3,7 +3,7 @@ import { test } from "bun:test";
|
||||
|
||||
import { createTaskTreeDispatcher } from "./dispatcher.ts";
|
||||
import { createTaskTreeHttpApp } from "./http.ts";
|
||||
import { TaskTreeStore } from "./store.ts";
|
||||
import { projectTimelineTasks, TaskTreeStore } from "./store.ts";
|
||||
|
||||
test("HTTP command endpoint returns the same dispatcher DTO", async () => {
|
||||
const group = {
|
||||
@@ -71,6 +71,35 @@ test("dispatcher keeps batch validation atomic and completion gated", async () =
|
||||
assert.equal(workflow.error?.code, "temporal_address_required");
|
||||
});
|
||||
|
||||
test("timeline projects lifecycle timestamps and expands parents around descendants", () => {
|
||||
const tasks = projectTimelineTasks([
|
||||
{
|
||||
id: "tt_root", groupId: "tg_1", parentId: null, kind: "task", title: "Root", description: "", status: "in_progress",
|
||||
startAt: "2026-07-21T10:00:00.000Z", dueAt: "2026-07-21T11:00:00.000Z", sortOrder: 0,
|
||||
createdAt: "2026-07-21T10:00:00.000Z", updatedAt: "2026-07-21T11:00:00.000Z"
|
||||
},
|
||||
{
|
||||
id: "tt_child", groupId: "tg_1", parentId: "tt_root", kind: "subtask", title: "Child", description: "", status: "completed",
|
||||
startAt: null, dueAt: null, sortOrder: 1,
|
||||
createdAt: "2026-07-21T09:00:00.000Z", updatedAt: "2026-07-21T12:00:00.000Z"
|
||||
},
|
||||
{
|
||||
id: "tt_grandchild", groupId: "tg_1", parentId: "tt_child", kind: "subsubtask", title: "Grandchild", description: "", status: "pending",
|
||||
startAt: null, dueAt: "2026-07-21T15:00:00.000Z", sortOrder: 2,
|
||||
createdAt: "2026-07-21T13:00:00.000Z", updatedAt: "2026-07-21T13:00:00.000Z"
|
||||
}
|
||||
], [{
|
||||
id: "tr_1", taskId: "tt_child", title: "Execution", body: "done", status: "succeeded",
|
||||
createdAt: "2026-07-21T14:00:00.000Z"
|
||||
}]);
|
||||
|
||||
assert.deepEqual(tasks.map(({ id, startAt, dueAt }) => ({ id, startAt, dueAt })), [
|
||||
{ id: "tt_root", startAt: "2026-07-21T09:00:00.000Z", dueAt: "2026-07-21T15:00:00.000Z" },
|
||||
{ id: "tt_child", startAt: "2026-07-21T09:00:00.000Z", dueAt: "2026-07-21T15:00:00.000Z" },
|
||||
{ id: "tt_grandchild", startAt: "2026-07-21T13:00:00.000Z", dueAt: "2026-07-21T15:00:00.000Z" }
|
||||
]);
|
||||
});
|
||||
|
||||
test("native PostgreSQL store exposes overview and three task levels", async () => {
|
||||
const databaseUrl = process.env.TASKTREE_DATABASE_URL;
|
||||
assert.ok(databaseUrl, "TASKTREE_DATABASE_URL is required for the TaskTree store integration test");
|
||||
@@ -146,6 +175,11 @@ test("native PostgreSQL store exposes overview and three task levels", async ()
|
||||
assert.equal(timeline?.tasks[2]?.kind, "subsubtask");
|
||||
assert.equal(timeline?.milestones.length, 1);
|
||||
assert.equal(timeline?.reports.length, 1);
|
||||
assert.ok(timeline?.tasks.every((item) => item.startAt !== null && item.dueAt !== null));
|
||||
const timelineStart = Math.min(...timeline!.tasks.map((item) => new Date(item.startAt!).valueOf()));
|
||||
const timelineDue = Math.max(...timeline!.tasks.map((item) => new Date(item.dueAt!).valueOf()));
|
||||
assert.equal(timeline?.tasks[0]?.startAt, new Date(timelineStart).toISOString());
|
||||
assert.equal(timeline?.tasks[0]?.dueAt, new Date(timelineDue).toISOString());
|
||||
assert.equal((await store.getTask(subtask.id))?.title, "Subtask");
|
||||
assert.equal((await store.listReports(task.id)).length, 1);
|
||||
assert.equal((await store.getReport(firstReport.report.id))?.body, "passed with evidence");
|
||||
@@ -158,8 +192,8 @@ test("native PostgreSQL store exposes overview and three task levels", async ()
|
||||
assert.equal(overview?.subtaskCount, 3);
|
||||
assert.equal(overview?.subsubtaskCount, 1);
|
||||
assert.equal(overview?.reportCount, 1);
|
||||
assert.equal(overview?.startAt, "2026-07-16T00:00:00.000Z");
|
||||
assert.equal(overview?.dueAt, "2026-07-18T00:00:00.000Z");
|
||||
assert.equal(overview?.startAt, new Date(timelineStart).toISOString());
|
||||
assert.equal(overview?.dueAt, new Date(timelineDue).toISOString());
|
||||
} finally {
|
||||
await store.deleteGroup(otherGroup.id);
|
||||
await store.deleteGroup(group.id);
|
||||
|
||||
Reference in New Issue
Block a user