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-selection.ts"); const temporary = fs.mkdtempSync(path.join(os.tmpdir(), "grease-pencil-selection-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-selection.mjs"); fs.writeFileSync(modulePath, transpiled.outputText); const selection = await import(pathToFileURL(modulePath)); const drawing = { dataId: "grease-pencil:Data", layerId: "grease-pencil-layer:Data:Lines", frame: 12, drawingId: "grease-pencil-drawing:Data:4", }; function point(strokeIndex, pointIndex) { return { ...drawing, strokeId: `grease-pencil-stroke:Data:4:${strokeIndex}`, pointId: `grease-pencil-point:Data:4:${strokeIndex}:${pointIndex}`, strokeIndex, pointIndex, }; } function edit(baseSelectionRevision, source, operation, points) { return { schemaVersion: 1, baseSelectionRevision, source, operation, points }; } test("M9-07 serializes 2D canvas and 3D viewport edits through one selection revision", () => { const initial = selection.createGreasePencilSelectionState(drawing); const canvas = selection.applyGreasePencilSelectionEdit(initial, edit(0, "CANVAS_2D", "REPLACE", [point(0, 1)]), drawing); assert.equal(canvas.revision, 1); assert.equal(canvas.lastSource, "CANVAS_2D"); assert.deepEqual(canvas.selectedPoints.map((item) => item.pointId), [point(0, 1).pointId]); const viewport = selection.applyGreasePencilSelectionEdit(canvas, edit(1, "VIEWPORT_3D", "ADD", [point(0, 0), point(1, 0)]), drawing); assert.equal(viewport.revision, 2); assert.equal(viewport.lastSource, "VIEWPORT_3D"); assert.deepEqual(viewport.selectedPoints.map((item) => item.pointId), [point(0, 0).pointId, point(0, 1).pointId, point(1, 0).pointId]); const toggled = selection.applyGreasePencilSelectionEdit(viewport, edit(2, "CANVAS_2D", "TOGGLE", [point(0, 1)]), drawing); assert.equal(toggled.revision, 3); assert.deepEqual(toggled.selectedPoints.map((item) => item.pointId), [point(0, 0).pointId, point(1, 0).pointId]); }); test("M9-07 rejects stale, foreign, duplicate and index-aliased selection edits", () => { const initial = selection.createGreasePencilSelectionState(drawing); assert.throws( () => selection.applyGreasePencilSelectionEdit(initial, edit(1, "VIEWPORT_3D", "REPLACE", [point(0, 0)]), drawing), (error) => error.code === "REVISION_CONFLICT", ); assert.throws( () => selection.applyGreasePencilSelectionEdit(initial, edit(0, "CANVAS_2D", "REPLACE", [{ ...point(0, 0), drawingId: "grease-pencil-drawing:Data:5" }]), drawing), (error) => error.code === "GREASE_PENCIL_SELECTION_SCOPE_INVALID", ); assert.throws( () => selection.applyGreasePencilSelectionEdit(initial, edit(0, "CANVAS_2D", "REPLACE", [point(0, 0), point(0, 0)]), drawing), (error) => error.code === "GREASE_PENCIL_SELECTION_INVALID", ); assert.throws( () => selection.applyGreasePencilSelectionEdit(initial, edit(0, "CANVAS_2D", "REPLACE", [point(0, 0), { ...point(0, 1), pointIndex: 0 }]), drawing), (error) => error.code === "GREASE_PENCIL_SELECTION_INVALID", ); });