Files
pikasTech-HWLAB/internal/project-management/mdtodo.test.ts
T

140 lines
5.0 KiB
TypeScript

import { expect, test } from "bun:test";
import { mutateMdtodoDocument, parseMdtodoDocument, readMdtodoTaskDetail } from "./mdtodo.ts";
test("parseMdtodoDocument projects Rxx hierarchy without raw body leakage", () => {
const document = parseMdtodoDocument(`# Demo
## R1 Root task [Processing]
Root notes with [context](https://example.test/context).
### R1.1 Child done [Finished]
- implementation notes stay in source markdown
## R2 Backlog item
`, {
sourceId: "demo",
relativePath: "project-management/MDTODO.md"
});
expect(document.parserMode).toBe("rxx");
expect(document.taskCount).toBe(3);
expect(document.tasks.map((task) => task.taskId)).toEqual(["R1", "R1.1", "R2"]);
expect(document.tasks.map((task) => task.status)).toEqual(["in_progress", "completed", "open"]);
expect(document.tasks[1].parentTaskRef).toBe(document.tasks[0].taskRef);
expect(document.tasks[0].depth).toBe(0);
expect(document.tasks[1].depth).toBe(1);
expect(document.tasks[0].linkCount).toBe(1);
expect(document.tasks[0].links).toEqual([
expect.objectContaining({ label: "context", kind: "external", relativePath: null })
]);
expect(document.tasks[0].bodyPreview).toBe("Root notes with [context](https://example.test/context).");
expect(document.tasks[0].body).toBeUndefined();
expect(document.tasks[0].rawContent).toBeUndefined();
expect(document.document.parserMode).toBe("rxx");
expect(document.document.valuesRedacted).toBe(true);
});
test("readMdtodoTaskDetail exposes Rxx body and safe Markdown report links", () => {
const detail = readMdtodoTaskDetail(`# Demo
## R1 Root task
Task details
- report: [R1.1](./20260110_LOG/R1.1_log_implementation.md)
- external: [context](https://example.test/context)
### R1.1 Child
Child details
`, {
sourceId: "demo",
relativePath: "20260609_feedback.md",
taskRef: "mdtodo:demo:file_plan:R1",
fileRef: "file_plan",
projectId: "project_test"
});
expect(detail?.body).toContain("Task details");
expect(detail?.body).not.toContain("Child details");
expect(detail?.links.find((link) => link.label === "R1.1")).toMatchObject({
kind: "markdown-report",
relativePath: "20260110_LOG/R1.1_log_implementation.md"
});
expect(detail?.links.find((link) => link.label === "context")).toMatchObject({ kind: "external", relativePath: null });
});
test("parseMdtodoDocument keeps legacy checkbox parsing explicit", () => {
const document = parseMdtodoDocument(`# Demo
- [ ] Root task
- [x] Child done
- [-] Blocked item
`, {
sourceId: "demo",
relativePath: "project-management/MDTODO.md"
});
expect(document.parserMode).toBe("legacy-checkbox");
expect(document.taskCount).toBe(3);
expect(document.tasks[0].status).toBe("open");
expect(document.tasks[1].status).toBe("completed");
expect(document.tasks[1].parentTaskRef).toBe(document.tasks[0].taskRef);
expect(document.tasks[2].status).toBe("open");
expect(document.document.valuesRedacted).toBe(true);
});
test("mutateMdtodoDocument supports Rxx add update delete operations", () => {
const initial = `# Demo
## R1 Root task
Original body.
### R1.1 Existing child
`;
let result = mutateMdtodoDocument(initial, { type: "addTask", title: "Second root" });
expect(result.ok).toBe(true);
expect(result.changedTaskId).toBe("R2");
expect(result.markdown).toContain("## R2 Second root");
result = mutateMdtodoDocument(result.markdown, { type: "addTask", parentRxxId: "R1", title: "Second child", body: "Child body" });
expect(result.changedTaskId).toBe("R1.2");
expect(result.markdown).toContain("### R1.2 Second child\n\nChild body");
result = mutateMdtodoDocument(result.markdown, { type: "addTask", afterRxxId: "R1.1", title: "Continue child" });
expect(result.changedTaskId).toBe("R1.3");
expect(result.markdown).toContain("### R1.3 Continue child");
result = mutateMdtodoDocument(result.markdown, { type: "updateStatus", rxxId: "R1", status: "completed" });
expect(result.markdown).toContain("## R1 Root task [completed]");
result = mutateMdtodoDocument(result.markdown, { type: "updateTitle", rxxId: "R1", title: "Renamed root" });
expect(result.markdown).toContain("## R1 Renamed root [completed]");
result = mutateMdtodoDocument(result.markdown, { type: "updateBody", rxxId: "R1", body: "Rewritten body." });
expect(result.markdown).toContain("## R1 Renamed root [completed]\n\nRewritten body.\n\n### R1.1 Existing child");
result = mutateMdtodoDocument(result.markdown, { type: "deleteTask", rxxId: "R1.2" });
expect(result.affectedTaskIds).toEqual(["R1.2"]);
expect(result.markdown).not.toContain("R1.2");
expect(result.document.tasks.map((task) => task.taskId)).toEqual(["R1", "R1.1", "R1.3", "R2"]);
});
test("mutateMdtodoDocument reports optimistic revision conflicts", () => {
const conflict = mutateMdtodoDocument("# Demo\n\n## R1 Root\n", {
type: "updateTitle",
rxxId: "R1",
title: "No write",
expectedFingerprint: "stale"
});
expect(conflict.ok).toBe(false);
expect(conflict.conflict).toBe(true);
expect(conflict.code).toBe("revision_conflict");
});