"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("")) 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/js/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"); });