// The manifest writers' guard (edit-guard.mjs) and `umtool window` through it: // an edit to a GENERATED manifest is an `edit` note for its generator; a // hand-edited manifest gets none. // // Run with: pnpm test:scripts import assert from "node:assert/strict"; import { execFileSync } from "node:child_process"; import { mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises"; import { tmpdir } from "node:os"; import path from "node:path"; import test from "node:test"; import { fileURLToPath } from "node:url"; import { readNotes } from "../annotations/store.mjs"; import { PROJECT_KINDS, kindTakesNotes } from "../projects/kinds.mjs"; import { updateClip } from "./manifest.mjs"; import { withEditNotes } from "./edit-guard.mjs"; const CLI = fileURLToPath(new URL("../../bin/umtool.mjs", import.meta.url)); // A kind that takes notes, from the registry -- never named here (projects.spec // refuses a kind id outside lib/projects/). const NOTES_KIND = PROJECT_KINDS.find((k) => kindTakesNotes(k.id)).id; async function project(manifest) { const root = await mkdtemp(path.join(tmpdir(), "umtool-editguard-")); const dir = path.join(root, "ws", "clipcut"); await mkdir(dir, { recursive: true }); await writeFile(path.join(dir, "video.manifest.json"), `${JSON.stringify(manifest, null, 2)}\n`); return { root, dir, project: { id: "ws/clipcut", dir, kind: NOTES_KIND } }; } const MANIFEST = (generated) => ({ schemaVersion: 1, slug: "clipcut", ...(generated ? { generatedBy: "polemics/video/make-videos.py" } : {}), timeline: [{ type: "clip", id: "e1", channel: "ch", video: "v1", start: 10, end: 20 }], }); test("an edit to a generated manifest is written, and noted for its generator", async () => { const { root, dir, project: p } = await project(MANIFEST(true)); try { const { result, editNotes } = await withEditNotes(p, () => updateClip(dir, "e1", { start: 12 }), { reportsRoot: root }); assert.equal(result.entry.start, 12); assert.deepEqual(editNotes, { generatedBy: "polemics/video/make-videos.py", added: 1, updated: 0, deleted: 0, errors: [] }); const { doc } = await readNotes(path.join(dir, "notes.json")); assert.equal(doc.notes.length, 1); assert.deepEqual(doc.notes[0].anchor, { kind: "edit", entry: "e1", field: "start", from: 10, to: 12 }); } finally { await rm(root, { recursive: true, force: true }); } }); test("a hand-edited manifest is written with no note", async () => { const { root, dir, project: p } = await project(MANIFEST(false)); try { const { editNotes } = await withEditNotes(p, () => updateClip(dir, "e1", { start: 12 }), { reportsRoot: root }); assert.equal(editNotes, null); await assert.rejects(readFile(path.join(dir, "notes.json"))); } finally { await rm(root, { recursive: true, force: true }); } }); test("`umtool window` on a generated manifest says it noted the edit, and the note is there", async () => { const { root, dir } = await project(MANIFEST(true)); try { const out = execFileSync(process.execPath, [CLI, "window", dir, "e1", "--start", "11", "--end", "21"], { cwd: root, env: { ...process.env, REPORTS_DIR: root }, encoding: "utf8", }); assert.match(out, /e1: 10–20 -> 11–21/); assert.match(out, /edit noted for polemics\/video\/make-videos\.py \(2 added/); const { doc } = await readNotes(path.join(dir, "notes.json")); assert.deepEqual(doc.notes.map((n) => n.anchor.field).sort(), ["end", "start"]); } finally { await rm(root, { recursive: true, force: true }); } });