Files
racket-wiki/test/cmap-layering.test.js
T
2026-09-03 08:40:15 +02:00

126 lines
3.7 KiB
JavaScript

"use strict";
const test = require("node:test");
const assert = require("node:assert/strict");
class FakeEventTarget {
constructor() {
this.listeners = new Map();
}
addEventListener(type, listener) {
const listeners = this.listeners.get(type) || [];
listeners.push(listener);
this.listeners.set(type, listeners);
}
removeEventListener(type, listener) {
this.listeners.set(type, (this.listeners.get(type) || [])
.filter((candidate) => candidate !== listener));
}
dispatchEvent(event) {
for (const listener of this.listeners.get(event.type) || []) listener(event);
}
}
class FakeElement extends FakeEventTarget {
constructor(tagName) {
super();
this.tagName = tagName.toUpperCase();
this.style = {};
this.attributes = {};
this.childNodes = [];
this.parentNode = null;
this.scrollLeft = 0;
this.scrollTop = 0;
this.scrollWidth = 1000;
this.scrollHeight = 800;
this.clientWidth = 1000;
this.clientHeight = 800;
this.textContent = "";
}
appendChild(child) {
if (child.parentNode) child.parentNode.removeChild(child);
child.parentNode = this;
this.childNodes.push(child);
return child;
}
removeChild(child) {
const index = this.childNodes.indexOf(child);
if (index >= 0) this.childNodes.splice(index, 1);
child.parentNode = null;
return child;
}
setAttribute(name, value) {
this.attributes[name] = String(value);
}
getBoundingClientRect() {
return { left: 0, top: 0, right: this.clientWidth, bottom: this.clientHeight };
}
set innerHTML(markup) {
this.childNodes = [];
if (!String(markup).includes("<svg>")) return;
const svg = new FakeElement("svg");
svg.appendChild(new FakeElement("path"));
svg.appendChild(new FakeElement("path"));
this.appendChild(svg);
this.appendChild(new FakeElement("div"));
}
}
function settleRendering() {
return new Promise((resolve) => setImmediate(() => setImmediate(resolve)));
}
test("relations render and hit-test behind concept nodes", async () => {
const root = new FakeElement("div");
const fakeDocument = new FakeEventTarget();
fakeDocument.body = root;
fakeDocument.createElement = (tagName) => new FakeElement(tagName);
global.document = fakeDocument;
global.window = {
requestAnimationFrame: (callback) => setImmediate(callback)
};
const { DiagramEngine } = await import("../static/cmap/cmap.js");
assert.equal(global.window.Cmap, undefined,
"the ES-module engine does not install a legacy global");
const map = new DiagramEngine(root);
const concept = map.node({ x: 40, y: 0, width: 120, height: 50 });
const otherConcept = map.node({ x: 220, y: 0, width: 120, height: 50 });
const relation = map.link({
sourceX: 0, sourceY: 25,
cx: 150, cy: 25,
targetX: 360, targetY: 25
});
relation.sourceNode(concept).targetNode(otherConcept).straighten();
assert.equal(relation.sourceNode(), concept);
assert.equal(relation.targetNode(), otherConcept);
await settleRendering();
relation.toFront();
await settleRendering();
assert.ok(Number(relation.element().style.zIndex) < Number(concept.element().style.zIndex));
assert.ok(Number(relation.element().style.zIndex) < Number(otherConcept.element().style.zIndex));
let selected = null;
map.onSelection((component) => { selected = component; });
const surface = root.childNodes[0];
surface.dispatchEvent({
type: "mousedown",
pageX: 100,
pageY: 25,
clientX: 100,
clientY: 25,
preventDefault() {}
});
fakeDocument.dispatchEvent({ type: "mouseup", pageX: 100, pageY: 25 });
assert.equal(selected, concept, "the concept wins when a relation crosses behind it");
});