Files
racket-wiki/test/cmap-selection-layout.test.js
T

154 lines
6.1 KiB
JavaScript

"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
};
}
async function loadEditorFactory() {
global.Element = class Element {};
global.document = {
currentScript: null,
styleSheets: [],
body: { append() {} },
createElement: () => ({ style: {}, remove() {} })
};
global.window = {
setTimeout,
clearTimeout,
requestAnimationFrame: (callback) => callback()
};
await import("../static/js/cmap/cmap-racket-wiki.js");
return global.window.RacketWikiCmap;
}
test("the last selected concept determines target size and alignment", async () => {
const factory = await 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, { createDiagramEngine: () => 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 });
assert.equal(editor.conceptRepository().concept(first.conceptId).label, "First");
assert.equal(editor.conceptMapModel().item(first.id).conceptId, first.conceptId);
assert.equal("node" in editor.conceptRepository().concept(first.conceptId), false,
"repository concepts never contain drawing nodes");
assert.equal("node" in editor.conceptMapModel().item(first.id), false,
"map placements never contain drawing nodes");
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, { createDiagramEngine: () => map });
reloaded.loadModel(editor.currentModel());
assert.equal(
reloaded.toDocument().concepts.find((concept) => concept.id === first.conceptId).externalUrl,
"https://example.com/first",
"external links survive an editor model 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) => setTimeout(resolve, 0));
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]]);
});