import { expect, test } from "bun:test"; import { mkdtemp, mkdir, writeFile } from "node:fs/promises"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { decodeEntities, naturalSort, slugify, uniqueId } from "../src/util/text"; import { run } from "../src/util/exec"; import { readRawManifest, writeManifest } from "../src/util/manifest"; test("slugify folds accents and falls back", () => { expect(slugify("Mastering Docker Essentials - Hands-on DevOps!")).toBe("mastering-docker-essentials-hands-on-devops"); expect(slugify("Café Ünïcode")).toBe("cafe-unicode"); expect(slugify("日本語")).toBe("course"); }); test("uniqueId appends a counter", () => { expect(uniqueId("a", (x) => x === "a" || x === "a-2")).toBe("a-3"); expect(uniqueId("a", () => false)).toBe("a"); }); test("decodeEntities handles named and numeric entities", () => { expect(decodeEntities("you'll & <b> "x" A")).toBe(`you'll & "x" A`); }); test("naturalSort orders numbers numerically", () => { expect(naturalSort(["10 b.mp4", "2 a.mp4", "1 z.mp4"])).toEqual(["1 z.mp4", "2 a.mp4", "10 b.mp4"]); }); test("run reports stderr on failure", async () => { expect(await run(["echo", "hi"])).toContain("hi"); await expect(run(["sh", "-c", "echo oops >&2; exit 3"])).rejects.toThrow(/oops/); }); test("writeManifest validates and round-trips", async () => { const dir = await mkdtemp(join(tmpdir(), "stc-m-")); await mkdir(join(dir, "c")); const m = { schema: 1, id: "x", title: "T: with colon", visibility: "restricted", modules: [{ id: "m1", title: "S08 - Foo", lessons: [] }] }; await writeManifest(join(dir, "c"), m); expect((await readRawManifest(join(dir, "c")) as any).modules[0].title).toBe("S08 - Foo"); await expect(writeManifest(join(dir, "c"), { ...m, id: "BAD ID" })).rejects.toThrow(/id/); await writeFile(join(dir, "c", "marker"), ""); });