Files
xboct-flow/src/frontmatter/frontmatterFields.test.ts
T

134 lines
3.9 KiB
TypeScript

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();
});
});