import { readFileSync, readdirSync } from "node:fs"; import { join } from "node:path"; import { describe, expect, it } from "vitest"; import { parseBoard, renderBoard } from "../src/model/board"; import { serializeTaskLine } from "../src/model/serialize"; const FIXTURES = join(import.meta.dirname, "fixtures"); const BOARDS = readdirSync(FIXTURES).filter((name) => name.endsWith(".md")); describe("board round-trip", () => { it("has boards to work with", () => { expect(BOARDS.length).toBeGreaterThan(0); }); for (const name of BOARDS) { const content = readFileSync(join(FIXTURES, name), "utf8"); const board = parseBoard(`Boards/${name}`, content); it(`${name}: re-renders byte for byte`, () => { expect(renderBoard(board)).toBe(content); }); it(`${name}: every task line survives parse then serialize`, () => { for (const task of board.tasks) { expect(serializeTaskLine(task), `line ${task.line + 1}`).toBe(task.raw); } }); it(`${name}: finds tasks and lanes`, () => { expect(board.lanes.length).toBeGreaterThan(0); expect(board.tasks.length).toBeGreaterThan(0); for (const task of board.tasks) { expect(board.lines[task.line]).toBe(task.raw); } }); } }); describe("field extraction", () => { const parse = (line: string) => { const board = parseBoard("x.md", `## lane\n\n${line}\n`); return board.tasks[0]; }; it("reads recurrence followed by dates", () => { const task = parse("- [ ] water the plants 🔁 every day 📅 2026-08-08"); expect(task.text).toBe("water the plants"); expect(task.recurrence).toBe("every day"); expect(task.due).toBe("2026-08-08"); expect(task.scheduled).toBeNull(); }); it("reads scheduled, due and done together", () => { const task = parse( "- [x] pay the electricity bill ⏳ 2026-07-28 📅 2026-07-29 ✅ 2026-07-28", ); expect(task.status).toBe("x"); expect(task.text).toBe("pay the electricity bill"); expect(task.scheduled).toBe("2026-07-28"); expect(task.due).toBe("2026-07-29"); expect(task.done).toBe("2026-07-28"); }); it("keeps wikilinks and embeds inside the description", () => { const task = parse("- [ ] call [[Alex Rivera|Alex]] about the move 📅 2026-08-09"); expect(task.text).toBe("call [[Alex Rivera|Alex]] about the move"); expect(task.due).toBe("2026-08-09"); }); it("does not mistake a url fragment for a tag", () => { const task = parse("- [ ] compare tiers https://example.com/pricing#plans"); expect(task.tags).toEqual([]); expect(task.text).toBe("compare tiers https://example.com/pricing#plans"); }); it("reads priority and writes it back in Tasks' order", () => { const task = parse("- [ ] fix the mobile nav ⏫ 📅 2026-08-10"); expect(task.priority).toBe(4); expect(serializeTaskLine(task)).toBe("- [ ] fix the mobile nav ⏫ 📅 2026-08-10"); }); it("captures a multi-line kanban card body", () => { const board = parseBoard( "x.md", "## lane\n\n- [x] rewrite the copy ✅ 2026-08-01\n\t- shorter intro\n\t- link the docs\n", ); expect(board.tasks[0].body).toEqual(["\t- shorter intro", "\t- link the docs"]); }); it("marks archive lanes", () => { const board = parseBoard( "x.md", "## live\n\n- [ ] a\n\n***\n\n## Archive\n\n- [x] b ✅ 2026-01-01\n", ); expect(board.tasks[0].inArchive).toBe(false); expect(board.tasks[1].inArchive).toBe(true); }); it("ignores anything inside the kanban settings block", () => { const board = parseBoard( "x.md", '## lane\n\n- [ ] real\n\n%% kanban:settings\n```\n{"kanban-plugin":"board"}\n```\n%%\n', ); expect(board.tasks).toHaveLength(1); }); });