Files
2026-10-30 02:49:42 +01:00

52 lines
2.1 KiB
TypeScript

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