import { readFileSync, readdirSync } from "node:fs"; import { describe, expect, it } from "vitest"; import { allTemplates, checkNote, checkVault, notePath, slugify, wikilinkTarget, writeNote } from "../src/index.ts"; describe("names", () => { it("slugs titles", () => { expect(slugify("Pokémon TCG")).toBe("pokemon-tcg"); expect(slugify("The Queen's Gambit")).toBe("the-queens-gambit"); expect(slugify("Dungeons & Dragons")).toBe("dungeons-and-dragons"); }); it("reads wikilinks with aliases and headings", () => { expect(wikilinkTarget("[[Galaxy dice|the galaxy set]]")).toBe("Galaxy dice"); expect(wikilinkTarget("[[Resin#Tools]]")).toBe("Resin"); expect(wikilinkTarget("Resin")).toBeNull(); }); }); describe("checkNote", () => { const status = `---\ntype: status\ntitle: Throne of Glass\npublish: true\nkind: Reading\ndate: 2026-10-11\nsubject: [[Throne of Glass]]\n---\n`; it("accepts an unquoted wikilink, as a hand edit might write it", () => { const r = checkNote(status); expect(r.ok).toBe(true); if (r.ok) expect(r.data.subject).toBe("[[Throne of Glass]]"); }); it("names the field that is wrong", () => { const r = checkNote(status.replace("date: 2026-10-11", "date: last tuesday")); expect(r.ok).toBe(false); if (!r.ok) expect(r.problems.map((p) => p.field)).toContain("date"); }); it("rejects unknown types and broken YAML without throwing", () => { expect(checkNote("---\ntype: recipe\ntitle: x\n---\n").ok).toBe(false); expect(checkNote("---\ntitle: [unclosed\n---\n").ok).toBe(false); }); it("defaults publish to false", () => { const r = checkNote("---\ntype: card-game\ntitle: Riftbound\nstance: Exploring\n---\n"); expect(r.ok && r.data.publish).toBe(false); }); it("requires a role on case studies", () => { const post = "---\ntype: post\ntitle: X\ndate: 2026-10-11\ntopic: Work and tech\npostType: case study\nsummary: Y\n---\n"; expect(checkNote(post).ok).toBe(false); expect(checkNote(post.replace("summary: Y", "summary: Y\nrole: Lead")).ok).toBe(true); }); }); describe("writeNote", () => { it("round-trips through checkNote", () => { const data = { type: "deck", title: "Kynaios and Tiro", publish: true, game: "[[Magic: The Gathering]]", format: "Commander" }; const r = checkNote(writeNote(data)); expect(r.ok && r.data).toMatchObject(data); }); it("refuses invalid data", () => { expect(() => writeNote({ type: "deck", title: "No game" })).toThrow(/game/); }); it("places notes by type", () => { expect(notePath({ type: "library-item", title: "Re:Zero", medium: "light novel" })).toBe("Library/Light novels/Re Zero.md"); expect(notePath({ type: "site-settings", title: "Site settings" })).toBe("_site.md"); }); }); describe("checkVault", () => { const n = (path: string, type: string, data: Record, body = "") => ({ path, type, data: { title: path, ...data }, body }); it("finds duplicate names, duplicate slugs and dangling links", () => { const problems = checkVault([ n("Shelf/Coup.md", "board-game", { slug: "coup" }), n("Library/Movies/Coup.md", "library-item", {}), n("Shelf/Other.md", "board-game", { slug: "coup" }), n("Posts/A.md", "post", { links: ["[[Missing]]"] }, "See [[Coup]] and ![[photo.jpg]]"), ], ["photo.jpg"]); const messages = problems.map((p) => p.message).join("\n"); expect(messages).toMatch(/Same file name/); expect(messages).toMatch(/Slug "coup"/); expect(messages).toMatch(/\[\[Missing\]\]/); expect(messages).not.toMatch(/photo\.jpg/); }); }); describe("templates", () => { it("match the committed vault kit", () => { const dir = new URL("../../../vault-kit/24.01_PersonalWebsite/Site/_templates/", import.meta.url); const committed = Object.fromEntries(readdirSync(dir).map((f) => [f.replace(/\.md$/, ""), readFileSync(new URL(f, dir), "utf8")])); for (const t of allTemplates()) expect(committed[t.name], `Run npm run templates -w @gk/schema`).toBe(t.text); expect(Object.keys(committed).length).toBe(allTemplates().length); }); });