Files
racket-wiki/test/cmap-connector-rewire.test.js
T

252 lines
6.6 KiB
JavaScript

"use strict";
const test = require("node:test");
const assert = require("node:assert/strict");
function fakeElement() {
return {
className: "rw-cmap-item",
style: {},
dataset: {},
querySelector: () => null,
querySelectorAll: () => [],
remove() {},
addEventListener() {},
classList: { add() {}, remove() {}, toggle() {} },
setAttribute() {},
removeAttribute() {}
};
}
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 fakeLink(attributes) {
const state = { ...attributes };
let source = null;
let target = null;
let connectionChangeHandler = null;
return {
attr(value) {
if (typeof value === "string") return state[value];
Object.assign(state, value);
return this;
},
sourceNode(value) {
if (arguments.length === 0) return source;
source = value;
return this;
},
targetNode(value) {
if (arguments.length === 0) return target;
target = value;
return this;
},
onConnectionChange(handler) { connectionChangeHandler = handler; },
triggerConnectionChange(type, node) {
if (type === "source") source = node;
if (type === "target") target = node;
connectionChangeHandler(this, type, node);
},
onRendered() {},
visible() {},
straighten() {},
draggable() {},
redraw() {},
remove() {},
element: () => null
};
}
async function loadEditorFactory() {
const previousFactory = global.window && global.window.RacketWikiCmap;
global.Element = class Element {};
global.document = {
currentScript: null,
styleSheets: [],
body: { append() {} },
createElement: () => fakeElement()
};
global.window = {
setTimeout,
clearTimeout,
requestAnimationFrame: (callback) => callback()
};
await import("../static/js/cmap/cmap-racket-wiki.js");
global.window.RacketWikiCmap = global.window.RacketWikiCmap || previousFactory;
return global.window.RacketWikiCmap;
}
test("a dragged connector endpoint survives collapsing and expanding its submap", async () => {
const factory = await loadEditorFactory();
const links = [];
const map = {
onSelection() {},
onActivation() {},
node: (attributes) => fakeNode(attributes),
link(attributes) {
const link = fakeLink(attributes);
links.push(link);
return link;
}
};
const canvas = {
addEventListener() {},
querySelector: () => null,
clientWidth: 1200,
clientHeight: 800,
scrollLeft: 0,
scrollTop: 0,
isConnected: true
};
const editor = factory.createEditor(canvas, { createDiagramEngine: () => map });
const submap = editor.addItem({
id: 1,
kind: "submap",
label: "Problemen en Symptomen LTS Model",
x: 300,
y: 200,
width: 320,
height: 70
});
submap.expanded = true;
submap.submapInitialized = true;
const problems = editor.addItem({
id: 2,
kind: "phrase",
label: "problemen",
parentSubmap: submap,
submapDepth: 1,
x: 400,
y: 300
});
const symptoms = editor.addItem({
id: 3,
kind: "phrase",
label: "Symptomen",
parentSubmap: submap,
submapDepth: 1,
x: 400,
y: 500
});
const concept = editor.addItem({
id: 4,
kind: "concept",
label: "Rob maakt veel fouten",
parentSubmap: submap,
submapDepth: 1,
x: 800,
y: 400
});
const connector = editor.addConnector(problems, concept, true, { id: 1 });
links[0].triggerConnectionChange("source", symptoms.node);
assert.equal(connector.source, symptoms,
"the editor model follows the endpoint dragged by the drawing library");
editor.toggleSubmap(submap, false);
editor.toggleSubmap(submap, true);
assert.equal(connector.source, symptoms);
assert.equal(connector.link.sourceNode(), symptoms.node);
assert.deepEqual(editor.toDocument().connectors, [{
id: 1,
sourceId: symptoms.id,
targetId: concept.id,
hasArrow: true,
lineColor: "#333",
lineWidth: 2
}]);
});
test("moving a concept does not move connected concepts or linking phrases", async () => {
const factory = await loadEditorFactory();
const map = {
onSelection() {},
onActivation() {},
node: (attributes) => fakeNode(attributes),
link: (attributes) => fakeLink(attributes)
};
const canvas = {
addEventListener() {},
querySelector: () => null,
clientWidth: 1200,
clientHeight: 800,
scrollLeft: 0,
scrollTop: 0,
isConnected: true
};
const editor = factory.createEditor(canvas, { createDiagramEngine: () => map });
const owner = editor.addItem({ id: 1, label: "Owner", x: 100, y: 100 });
const phrase = editor.addItem({ id: 2, kind: "phrase", label: "relates to", x: 260, y: 100 });
const linked = editor.addItem({
id: 3,
label: "Linked CMap concept",
cmapSlug: "other-map",
x: 420,
y: 100
});
editor.addConnector(owner, phrase, true, { id: 1 });
editor.addConnector(phrase, linked, true, { id: 2 });
editor.selectItem(owner);
owner.node.attr(editor.handleItemMove(owner, 180, 140));
assert.deepEqual({
owner: [owner.node.attr("x"), owner.node.attr("y")],
phrase: [phrase.node.attr("x"), phrase.node.attr("y")],
linked: [linked.node.attr("x"), linked.node.attr("y")]
}, {
owner: [180, 140],
phrase: [260, 100],
linked: [420, 100]
});
});
test("moving a concept schedules one boundary-reference geometry refresh", async () => {
const factory = await loadEditorFactory();
const frames = [];
global.window.requestAnimationFrame = (callback) => frames.push(callback);
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 concept = editor.addItem({ id: 1, label: "Inside concept", x: 100, y: 100 });
let refreshCount = 0;
editor.refreshBoundaryReferences = () => { refreshCount += 1; };
editor.handleItemMove(concept, 150, 130);
editor.handleItemMove(concept, 170, 140);
assert.equal(frames.length, 1, "pointer moves in one frame are coalesced");
frames[0]();
assert.equal(refreshCount, 1);
});