import test from "node:test"; import assert from "node:assert/strict"; import { CmapJsonExporter } from "../static/js/cmap/model/json-exporter.js"; import { CmapJsonImporter } from "../static/js/cmap/model/json-importer.js"; import { CmapRepository, StoredConceptMap } from "../static/js/cmap/model/cmap-repository.js"; import { CmapModel } from "../static/js/cmap/model/concept-map.js"; const conceptId = "11111111-1111-4111-8111-111111111111"; const rootMap = { slug: "root", title: "Root", document: { schemaVersion: 2, metadata: { explanationPageSlug: "root-page" }, concepts: [{ id: conceptId, label: "Root concept" }], items: [{ id: 1, conceptId, kind: "concept", x: 10, y: 20 }], connectors: [] } }; const page = { slug: "root-page", title: "Root page", markdown: "![Image](/uploads/root-page/image.png)", tags: [] }; const storedRootMap = new StoredConceptMap(rootMap, CmapModel.fromDocument(rootMap.document)); const cmapRepository = { load: async () => { throw new Error("no linked map expected"); } }; test("CmapJsonExporter builds a complete bundle including attachments", async () => { const exporter = new CmapJsonExporter( cmapRepository, async () => page, async () => ({ ok: true, headers: { get: () => "image/png" }, arrayBuffer: async () => Uint8Array.from([1, 2, 3]).buffer }), "test" ); const bundle = await exporter.export(storedRootMap); assert.equal(bundle.generator, "test"); assert.equal(bundle.pages[0].attachments[0].contentBase64, "AQID"); }); test("CmapJsonImporter reports conflicts and imports pages, attachments and maps", async () => { const calls = []; const api = async (path, options = {}) => { calls.push({ path, options }); if (path.endsWith("/upload")) return { url: "/uploads/root-page/imported-image.png" }; if (path === "/api/pages" && options.method === "POST") return { currentVersion: 1 }; if (path === "/api/cmaps" && options.method === "POST") { const body = JSON.parse(options.body); return { slug: body.slug, title: body.title, currentVersion: 1, document: body.document }; } return {}; }; const importer = new CmapJsonImporter( new CmapRepository(api), api, (_key, fallback) => fallback); const exporter = new CmapJsonExporter( cmapRepository, async () => page, async () => ({ ok: true, headers: { get: () => "image/png" }, arrayBuffer: async () => Uint8Array.from([1, 2, 3]).buffer }) ); const bundle = await exporter.export(storedRootMap); const file = { size: 100, text: async () => JSON.stringify(bundle) }; const parsed = await importer.read(file); assert.equal(parsed.rootCmapSlug, "root"); assert.deepEqual(importer.conflicts(parsed, [{ slug: "root-page" }], []), { pages: [parsed.pages[0]], conceptMaps: [] }); const result = await importer.import(parsed, { pages: [], conceptMaps: [], summary: "Imported" }); assert.equal(result.pagesCreated, 1); assert.equal(result.mapsCreated, 1); assert.equal(result.attachmentsImported, 1); assert.deepEqual(calls.map((call) => `${call.options.method} ${call.path}`), [ "POST /api/pages", "POST /api/pages/root-page/upload", "PUT /api/pages/root-page", "POST /api/cmaps" ]); const pageUpdate = JSON.parse(calls[2].options.body); assert.match(pageUpdate.markdown, /imported-image\.png/); });