added mermaid and a lot of cmap changes

This commit is contained in:
2026-08-27 13:13:39 +02:00
parent 20c1584016
commit 2215d1d04a
35 changed files with 6442 additions and 350 deletions
+148
View File
@@ -0,0 +1,148 @@
"use strict";
const test = require("node:test");
const assert = require("node:assert/strict");
function fakeNode(attributes) {
const state = { ...attributes };
return {
attr(value) {
if (typeof value === "string") return state[value];
Object.assign(state, value);
return this;
},
onRendered() {}, onMove() {}, onMoveEnd() {}, visible() {}, redraw() {},
toFront() {}, remove() {}, element: () => null
};
}
function loadEditorFactory() {
global.Element = class Element {};
global.document = {
currentScript: null,
styleSheets: [],
body: { append() {} },
createElement: () => ({ style: {}, remove() {} })
};
global.window = {
Cmap: null,
setTimeout,
clearTimeout,
requestAnimationFrame: (callback) => callback()
};
require("../static/cmap/cmap-racket-wiki.js");
return global.window.RacketWikiCmap;
}
test("the last selected concept determines target size and alignment", async () => {
const factory = loadEditorFactory();
const map = {
onSelection() {},
onActivation() {},
node: (attributes) => fakeNode(attributes)
};
const canvas = {
addEventListener() {}, querySelector: () => null,
clientWidth: 1200, clientHeight: 800, scrollLeft: 0, scrollTop: 0,
isConnected: true
};
const editor = factory.createEditor(canvas, { Cmap: () => map });
const first = editor.addItem({
id: 1, label: "First", externalUrl: "https://example.com/first",
x: 10, y: 10, width: 100, height: 40
});
const second = editor.addItem({ id: 2, label: "Second", x: 80, y: 100, width: 120, height: 45 });
const third = editor.addItem({ id: 3, label: "Third", x: 250, y: 250, width: 200, height: 60 });
const savedDocument = editor.toDocument();
assert.equal(
savedDocument.concepts.find((concept) => concept.id === first.conceptId).externalUrl,
"https://example.com/first",
"external links are serialized as shared concept content"
);
const reloaded = factory.createEditor(canvas, { Cmap: () => map });
reloaded.loadDocument(savedDocument);
assert.equal(
reloaded.toDocument().concepts.find((concept) => concept.id === first.conceptId).externalUrl,
"https://example.com/first",
"external links survive an editor save and reload round-trip"
);
editor.selectItem(first);
editor.selectItem(third, { additive: true });
editor.selectItem(second, { additive: true });
editor.resetHistory();
assert.equal(editor.selected(), second, "the last selected concept is the target object");
assert.equal(editor.canLayoutSelection("distribute-horizontal"), true);
assert.equal(editor.canLayoutSelection("distribute-vertical"), true);
editor.applySelectionLayout("align-right");
assert.deepEqual([first, second, third].map((item) => Number(item.node.attr("x"))),
[100, 80, 0]);
first.node.attr({ x: 10, y: 10 });
second.node.attr({ x: 80, y: 100 });
third.node.attr({ x: 250, y: 250 });
editor.applySelectionLayout("align-center");
assert.deepEqual([first, second, third].map((item) => Number(item.node.attr("x"))),
[90, 80, 40]);
first.node.attr({ x: 10, y: 10 });
second.node.attr({ x: 80, y: 100 });
third.node.attr({ x: 250, y: 250 });
editor.applySelectionLayout("align-left");
assert.deepEqual([first, second, third].map((item) => Number(item.node.attr("x"))),
[80, 80, 80]);
first.node.attr({ x: 10, y: 10 });
second.node.attr({ x: 80, y: 100 });
third.node.attr({ x: 250, y: 250 });
editor.applySelectionLayout("align-bottom");
assert.deepEqual([first, second, third].map((item) => Number(item.node.attr("y"))),
[105, 100, 85]);
first.node.attr({ x: 10, y: 10 });
second.node.attr({ x: 80, y: 100 });
third.node.attr({ x: 250, y: 250 });
editor.applySelectionLayout("align-middle");
assert.deepEqual([first, second, third].map((item) => Number(item.node.attr("y"))),
[102.5, 100, 92.5]);
first.node.attr({ x: 10, y: 10 });
second.node.attr({ x: 80, y: 100 });
third.node.attr({ x: 250, y: 250 });
editor.applySelectionLayout("align-top");
assert.deepEqual([first, second, third].map((item) => Number(item.node.attr("y"))),
[100, 100, 100]);
first.node.attr({ x: 10, y: 10 });
second.node.attr({ x: 80, y: 100 });
third.node.attr({ x: 250, y: 250 });
editor.applySelectionLayout("distribute-horizontal");
assert.deepEqual([first, second, third].map((item) => Number(item.node.attr("x"))),
[10, 120, 250], "outer items stay fixed and horizontal gaps become equal");
first.node.attr({ x: 10, y: 10 });
second.node.attr({ x: 80, y: 100 });
third.node.attr({ x: 250, y: 250 });
editor.applySelectionLayout("distribute-vertical");
assert.deepEqual([first, second, third].map((item) => Number(item.node.attr("y"))),
[10, 127.5, 250], "outer items stay fixed and the middle item receives an equal gap");
editor.applySelectionLayout("same-width");
assert.deepEqual([first, second, third].map((item) => Number(item.node.attr("width"))),
[120, 120, 120]);
editor.applySelectionLayout("same-height");
assert.deepEqual([first, second, third].map((item) => Number(item.node.attr("height"))),
[45, 45, 45]);
first.node.attr({ width: 90, height: 35 });
third.node.attr({ width: 180, height: 70 });
editor.applySelectionLayout("same-size");
assert.deepEqual([first, second, third].map((item) =>
[Number(item.node.attr("width")), Number(item.node.attr("height"))]),
[[120, 45], [120, 45], [120, 45]]);
await new Promise((resolve) => setImmediate(resolve));
assert.equal(editor.canUndo(), true, "layout commands participate in undo and autosave history");
assert.deepEqual(editor.toDocument().items.map((item) => [item.width, item.height]),
[[120, 45], [120, 45], [120, 45]]);
});