This commit is contained in:
2026-07-12 21:26:08 +08:00
commit 9dd41afd48
502 changed files with 129901 additions and 0 deletions
@@ -0,0 +1,96 @@
import { describe, it } from "node:test";
import assert from "node:assert/strict";
import {
beginGraphNodeDrag,
resolveGraphNodeDragTarget,
screenPointToWorldPoint,
worldPointToScreenPoint
} from "../src/render";
import type { GraphScreenPoint, GraphWorldPoint, RendererViewport, RendererViewportSize } from "../src/render";
const VIEWPORT_SIZE: RendererViewportSize = { width: 1000, height: 680 };
function assertNear(actual: number, expected: number, message?: string): void {
assert.ok(Math.abs(actual - expected) < 0.01, `${message ?? "number"}: expected ${expected}, got ${actual}`);
}
function assertPointNear(actual: { x: number; y: number }, expected: { x: number; y: number }): void {
assertNear(actual.x, expected.x, "x");
assertNear(actual.y, expected.y, "y");
}
describe("graph node drag simulation bridge", () => {
it("keeps the grabbed point under the pointer when the viewport is panned and zoomed", () => {
const viewport: RendererViewport = { x: -180, y: 72, scale: 2.4 };
const nodeWorldPoint: GraphWorldPoint = { x: 360, y: 240 };
const nodeScreenPoint = worldPointToScreenPoint(nodeWorldPoint, viewport, VIEWPORT_SIZE);
const pointerScreenPoint: GraphScreenPoint = {
x: nodeScreenPoint.x + 30,
y: nodeScreenPoint.y - 18
};
const drag = beginGraphNodeDrag({
nodeWorldPoint,
pointerScreenPoint,
viewport,
viewportSize: VIEWPORT_SIZE
});
const movedPointer: GraphScreenPoint = {
x: pointerScreenPoint.x + 144,
y: pointerScreenPoint.y + 96
};
const target = resolveGraphNodeDragTarget({
pointerScreenPoint: movedPointer,
viewport,
viewportSize: VIEWPORT_SIZE,
grabOffset: drag.grabOffset
});
const grabbedWorldPoint = {
x: target.x + drag.grabOffset.x,
y: target.y + drag.grabOffset.y
};
assertPointNear(worldPointToScreenPoint(grabbedWorldPoint, viewport, VIEWPORT_SIZE), movedPointer);
});
it("starts a drag at the current node point instead of jumping to the pointer center", () => {
const viewport: RendererViewport = { x: -320, y: -140, scale: 1.75 };
const nodeWorldPoint: GraphWorldPoint = { x: 700, y: 410 };
const pointerScreenPoint: GraphScreenPoint = {
x: worldPointToScreenPoint(nodeWorldPoint, viewport, VIEWPORT_SIZE).x - 48,
y: worldPointToScreenPoint(nodeWorldPoint, viewport, VIEWPORT_SIZE).y + 22
};
const drag = beginGraphNodeDrag({
nodeWorldPoint,
pointerScreenPoint,
viewport,
viewportSize: VIEWPORT_SIZE
});
assertPointNear(drag.targetWorldPoint, nodeWorldPoint);
assertPointNear(drag.pointerWorldPoint, screenPointToWorldPoint(pointerScreenPoint, viewport, VIEWPORT_SIZE));
});
it("does not clamp off-world drag targets inside projection or bridge helpers", () => {
const viewport: RendererViewport = { x: -120, y: -80, scale: 1.2 };
const nodeWorldPoint: GraphWorldPoint = { x: 980, y: 650 };
const drag = beginGraphNodeDrag({
nodeWorldPoint,
pointerScreenPoint: worldPointToScreenPoint(nodeWorldPoint, viewport, VIEWPORT_SIZE),
viewport,
viewportSize: VIEWPORT_SIZE
});
const target = resolveGraphNodeDragTarget({
pointerScreenPoint: { x: 1380, y: 930 },
viewport,
viewportSize: VIEWPORT_SIZE,
grabOffset: drag.grabOffset
});
assert.ok(target.x > 1000, `x should remain outside layout bounds before simulation constraints, got ${target.x}`);
assert.ok(target.y > 680, `y should remain outside layout bounds before simulation constraints, got ${target.y}`);
});
});