import { describe, expect, it } from "vitest"; import { TaskPriority } from "@/model/taskPriority"; import { TaskStatus } from "@/model/taskStatus"; import { extractFrontmatterBlock, findFieldRange, formatTagsYamlValue, replaceFrontmatterField, setFrontmatterTags, } from "./frontmatterFields"; const sample = `--- id: abc title: Hello status: todo priority: medium tags: [old] --- Body `; describe("replaceFrontmatterField", () => { it("replaces status value", () => { const result = replaceFrontmatterField(sample, "status", TaskStatus.DONE); expect(result.ok).toBe(true); if (!result.ok) return; expect(result.content).toContain("status: done"); expect(result.content).toContain("title: Hello"); expect(result.content).toContain("Body"); }); it("replaces priority value", () => { const result = replaceFrontmatterField(sample, "priority", TaskPriority.CRITICAL); expect(result.ok).toBe(true); if (!result.ok) return; expect(result.content).toContain("priority: critical"); }); it("fails when field is missing", () => { const result = replaceFrontmatterField(sample, "assignee", "me"); expect(result).toEqual({ ok: false, reason: "no-field" }); }); it("fails without frontmatter", () => { const result = replaceFrontmatterField("plain", "status", "todo"); expect(result).toEqual({ ok: false, reason: "no-frontmatter" }); }); }); describe("findFieldRange", () => { it("points at the value column", () => { const range = findFieldRange(sample, "status"); expect(range).toBeDefined(); const line = sample.split("\n")[range!.startLine]; expect(line.slice(range!.startCol, range!.endCol)).toBe("todo"); }); }); describe("formatTagsYamlValue", () => { it("formats empty and simple lists", () => { expect(formatTagsYamlValue([])).toBe("[]"); expect(formatTagsYamlValue(["bug", "frontend"])).toBe( "[bug, frontend]", ); }); it("quotes tags with spaces", () => { expect(formatTagsYamlValue(["my tag"])).toBe('["my tag"]'); }); }); describe("setFrontmatterTags", () => { it("replaces the tags line", () => { const result = setFrontmatterTags(sample, ["bug", "docs"]); expect(result.ok).toBe(true); if (!result.ok) return; expect(result.content).toContain("tags: [bug, docs]"); expect(result.content).not.toContain("tags: [old]"); }); }); describe("extractFrontmatterBlock", () => { it("extracts yaml from a valid block", () => { const block = extractFrontmatterBlock( "---\ntitle: Hello\nstatus: todo\n---\nbody", ); expect(block).not.toBeNull(); expect(block!.yaml).toBe("title: Hello\nstatus: todo"); expect(block!.closeLine).toBe(3); }); it("returns null when content does not start with ---", () => { expect(extractFrontmatterBlock("no frontmatter")).toBeNull(); expect(extractFrontmatterBlock("")).toBeNull(); }); it("handles unclosed frontmatter with trailing newline", () => { const block = extractFrontmatterBlock("---\ntitle: x\n"); expect(block).not.toBeNull(); expect(block!.closeLine).toBe(-1); expect(block!.yaml).toBe("title: x\n"); }); it("handles unclosed frontmatter without trailing newline", () => { const block = extractFrontmatterBlock("---\ntitle: x"); expect(block).not.toBeNull(); expect(block!.closeLine).toBe(-1); expect(block!.yaml).toBe("title: x"); }); it("handles CRLF line endings (yaml is normalized to LF)", () => { const block = extractFrontmatterBlock( "---\r\ntitle: Hello\r\nstatus: todo\r\n---\r\nbody", ); expect(block).not.toBeNull(); expect(block!.yaml).toBe("title: Hello\nstatus: todo"); }); it("handles --- with trailing whitespace", () => { const block = extractFrontmatterBlock( "--- \ntitle: Hello\n--- \nbody", ); expect(block).not.toBeNull(); expect(block!.yaml).toBe("title: Hello"); expect(block!.closeLine).toBe(2); }); it("only matches --- on the first line", () => { const block = extractFrontmatterBlock("text\n---\ntitle: x\n---\n"); expect(block).toBeNull(); }); });