import { describe, expect, test } from "bun:test"; import { parseManifest } from "../src/schema"; const base = (extra = "", lessons = " - { id: l1, title: One, type: text, file: a.md }") => `schema: 1\nid: x\ntitle: X\n${extra}modules:\n - id: m1\n title: M\n lessons:\n${lessons}\n`; describe("parseManifest", () => { test("parses a minimal manifest", () => { const r = parseManifest(base()); expect(r.ok).toBe(true); if (r.ok) { expect(r.manifest.visibility).toBe("restricted"); expect(r.manifest.gating).toBe("none"); } }); test("rejects a bad accent", () => { const r = parseManifest(base('accent: "orange"\n')); expect(r.ok).toBe(false); if (!r.ok) expect(r.errors.some((e) => e.includes("accent"))).toBe(true); }); test("rejects duplicate lesson ids across modules", () => { const text = base() + " - id: m2\n title: M2\n lessons:\n - { id: l1, title: Dup, type: text, file: b.md }\n"; const r = parseManifest(text); expect(r.ok).toBe(false); if (!r.ok) expect(r.errors.some((e) => e.includes("l1"))).toBe(true); }); test("requires file for text and video lessons", () => { for (const type of ["text", "video"]) { const r = parseManifest(base("", ` - { id: l1, title: One, type: ${type} }`)); expect(r.ok).toBe(false); if (!r.ok) expect(r.errors.some((e) => e.includes("file"))).toBe(true); } expect(parseManifest(base("", " - { id: l1, title: One, type: exercise }")).ok).toBe(true); }); test("rejects a deck whose attachTo is not a lesson or module id", () => { const r = parseManifest(base("decks:\n - { id: d1, file: d.pdf, attachTo: nope }\n")); expect(r.ok).toBe(false); if (!r.ok) expect(r.errors.some((e) => e.includes("nope"))).toBe(true); expect(parseManifest(base("decks:\n - { id: d1, file: d.pdf, attachTo: m1 }\n")).ok).toBe(true); }); test("rejects schema: 2", () => { const r = parseManifest(base().replace("schema: 1", "schema: 2")); expect(r.ok).toBe(false); if (!r.ok) expect(r.errors.some((e) => e.startsWith("schema"))).toBe(true); }); });