import assert from "node:assert/strict"; import fs from "node:fs"; import os from "node:os"; import path from "node:path"; import { pathToFileURL } from "node:url"; import test from "node:test"; import ts from "typescript"; const repoRoot = path.resolve(import.meta.dirname, "../../.."); const sourcePath = path.join(repoRoot, "web/protocol/grease-pencil-reorder.ts"); const temporary = fs.mkdtempSync(path.join(os.tmpdir(), "grease-pencil-reorder-unit-")); const transpiled = ts.transpileModule(fs.readFileSync(sourcePath, "utf8"), { compilerOptions: { module: ts.ModuleKind.ES2022, target: ts.ScriptTarget.ES2022 }, fileName: sourcePath, reportDiagnostics: true, }); assert.deepEqual(transpiled.diagnostics, []); const modulePath = path.join(temporary, "grease-pencil-reorder.mjs"); fs.writeFileSync(modulePath, transpiled.outputText); const reorder = await import(pathToFileURL(modulePath)); const dataId = "grease-pencil:Data"; const linesId = "grease-pencil-layer:Data:Lines"; const draftsId = "grease-pencil-layer:Data:Web Drafts"; const drawingId = "grease-pencil-drawing:Data:4"; const drawing = { id: drawingId, strokeCount: 0, pointCount: 0, strokes: [] }; const data = [{ id: dataId, name: "Data", geometryStatus: "available", layerCount: 2, frameCount: 2, strokeCount: 0, pointCount: 0, layers: [ { id: linesId, name: "Lines", visible: true, locked: false, opacity: 1, frames: [{ frame: 1, drawing: { ...drawing, id: "grease-pencil-drawing:Data:0" } }] }, { id: draftsId, name: "Web Drafts", visible: true, locked: false, opacity: 1, frames: [{ frame: 1, drawing }] }, ], }]; test("M9-08 binds layer and frame reorder commands to stable IDs and Main revision", () => { const layer = reorder.validateGreasePencilReorderCommand({ type: "moveGreasePencilLayer", schemaVersion: 1, dataId, layerId: draftsId, direction: "DOWN", baseRevision: 27, }, 27, data); assert.deepEqual(layer, { type: "moveGreasePencilLayer", schemaVersion: 1, dataId, layerId: draftsId, direction: "DOWN", baseRevision: 27, }); const frame = reorder.validateGreasePencilReorderCommand({ type: "moveGreasePencilFrame", schemaVersion: 1, dataId, layerId: draftsId, frame: 1, targetFrame: 12, drawingId, baseRevision: 27, }, 27, data); assert.equal(frame.drawingId, drawingId); assert.equal(frame.targetFrame, 12); }); test("M9-08 rejects stale, no-op, forged drawing and occupied-target reorders", () => { const layerCommand = { type: "moveGreasePencilLayer", schemaVersion: 1, dataId, layerId: draftsId, direction: "DOWN", baseRevision: 27 }; assert.throws(() => reorder.validateGreasePencilReorderCommand(layerCommand, 28, data), (error) => error.code === "REVISION_CONFLICT"); assert.throws(() => reorder.validateGreasePencilReorderCommand({ ...layerCommand, layerId: linesId }, 27, data), (error) => error.code === "GREASE_PENCIL_SCHEMA_INVALID"); const frameCommand = { type: "moveGreasePencilFrame", schemaVersion: 1, dataId, layerId: draftsId, frame: 1, targetFrame: 12, drawingId, baseRevision: 27 }; assert.throws(() => reorder.validateGreasePencilReorderCommand({ ...frameCommand, drawingId: "grease-pencil-drawing:Data:5" }, 27, data), (error) => error.code === "GREASE_PENCIL_SCHEMA_INVALID"); assert.throws(() => reorder.validateGreasePencilReorderCommand({ ...frameCommand, targetFrame: 1 }, 27, data), (error) => error.code === "GREASE_PENCIL_SCHEMA_INVALID"); const occupied = [{ ...data[0], layers: data[0].layers.map((layer) => layer.id === draftsId ? { ...layer, frames: [...layer.frames, { frame: 12, drawing: { ...drawing, id: "grease-pencil-drawing:Data:5" } }] } : layer) }]; assert.throws(() => reorder.validateGreasePencilReorderCommand(frameCommand, 27, occupied), (error) => error.code === "GREASE_PENCIL_SCHEMA_INVALID"); });