import { describe, it } from "node:test"; import assert from "node:assert/strict"; import { GRAPH_MINIMAP_VIEWBOX, GRAPH_WORLD_SIZE, layerDeltaToWorldDelta, layerPointToWorldPoint, minimapPointToWorldPoint, rootClientPointToScreenPoint, screenPointToWorldPoint, sideExitWorldAnchor, svgPointToWorldPoint, visibleWorldRectForViewport, visibleWorldRectToMinimapRect, worldDeltaToLayerDelta, worldPointDeltaToLayerDelta, worldPointToCssPercentPoint, worldPointToLayerPoint, worldPointToMinimapPoint, worldPointToScreenPoint, worldPointToSvgPoint } from "../src/render"; import type { RendererViewport, RendererViewportSize } from "../src/render"; const VIEWPORT_SIZE: RendererViewportSize = { width: 1200, height: 816 }; 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 geometry projection", () => { for (const scale of [0.5, 1, 2.25]) { it(`round trips world and screen points at scale ${scale}`, () => { const viewport: RendererViewport = { x: -180.5, y: 74.25, scale }; const worldPoint = { x: 812.34, y: 123.45 }; const screenPoint = worldPointToScreenPoint(worldPoint, viewport, VIEWPORT_SIZE); const roundTrip = screenPointToWorldPoint(screenPoint, viewport, VIEWPORT_SIZE); assertPointNear(roundTrip, worldPoint); }); } it("projects through viewport pan and zoom", () => { const viewport: RendererViewport = { x: 120, y: -80, scale: 1.75 }; const worldPoint = { x: 500, y: 340 }; assertPointNear( worldPointToScreenPoint(worldPoint, viewport, { width: 1000, height: 680 }), { x: 995, y: 515 } ); }); it("converts root client point to graph screen point", () => { const screenPoint = rootClientPointToScreenPoint( { x: 940, y: 520 }, { left: 270, top: 88, width: 1000, height: 680 } ); assert.deepEqual(screenPoint, { x: 670, y: 432 }); }); it("does not silently clamp screen to world projection outside the visible root", () => { const viewport: RendererViewport = { x: -200, y: -100, scale: 2 }; const worldPoint = screenPointToWorldPoint({ x: -240, y: 1500 }, viewport, { width: 1000, height: 680 }); assert.ok(worldPoint.x < 0, `x should remain outside world for drag handling, got ${worldPoint.x}`); assert.ok(worldPoint.y > GRAPH_WORLD_SIZE.height, `y should remain outside world for drag handling, got ${worldPoint.y}`); }); it("round trips world and layer points after drawer-style viewport resize", () => { const worldPoint = { x: 760, y: 340 }; const beforeLayer = worldPointToLayerPoint(worldPoint, { width: 1170, height: 856 }); const afterLayer = worldPointToLayerPoint(worldPoint, { width: 750, height: 856 }); assert.notEqual(beforeLayer.x, afterLayer.x); assertPointNear(layerPointToWorldPoint(afterLayer, { width: 750, height: 856 }), worldPoint); }); it("converts deltas without treating them as absolute points", () => { const worldDelta = { x: 50, y: -34 }; const layerDelta = worldDeltaToLayerDelta(worldDelta, VIEWPORT_SIZE); const roundTrip = layerDeltaToWorldDelta(layerDelta, VIEWPORT_SIZE); assertPointNear(roundTrip, worldDelta); assertPointNear( worldPointDeltaToLayerDelta({ x: 220, y: 180 }, { x: 270, y: 146 }, VIEWPORT_SIZE), layerDelta ); }); it("maps out-of-world positions to css percentages without clamping them", () => { const cssPoint = worldPointToCssPercentPoint({ x: 1240, y: 816 }); assertPointNear(cssPoint, { x: 124, y: 120 }); }); it("maps expanded world bounds without clamping or drifting coordinates", () => { const bounds = { minX: -200, minY: 0, maxX: 1320, maxY: 896, width: 1520, height: 896 }; const worldPoint = { x: 1240, y: 816 }; const layerPoint = worldPointToLayerPoint(worldPoint, VIEWPORT_SIZE, bounds); const cssPoint = worldPointToCssPercentPoint(worldPoint, bounds); const minimapPoint = worldPointToMinimapPoint(worldPoint, undefined, bounds); assertPointNear(layerPointToWorldPoint(layerPoint, VIEWPORT_SIZE, bounds), worldPoint); assertPointNear(cssPoint, { x: 94.737, y: 91.071 }); assertPointNear(minimapPointToWorldPoint(minimapPoint, undefined, bounds), worldPoint); }); it("keeps svg point naming explicit even while svg and world share the same domain", () => { const worldPoint = { x: 220.5, y: 410.25 }; const svgPoint = worldPointToSvgPoint(worldPoint); assert.deepEqual(svgPoint, worldPoint); assert.deepEqual(svgPointToWorldPoint(svgPoint), worldPoint); }); it("maps world and viewport rectangles to minimap coordinates", () => { const minimapPoint = worldPointToMinimapPoint({ x: 500, y: 340 }); const worldPoint = minimapPointToWorldPoint(minimapPoint); const viewportRect = visibleWorldRectForViewport( { x: -250, y: -170, scale: 2 }, { width: 1000, height: 680 } ); const minimapRect = visibleWorldRectToMinimapRect(viewportRect); assertPointNear(minimapPoint, { x: GRAPH_MINIMAP_VIEWBOX.x + GRAPH_MINIMAP_VIEWBOX.width / 2, y: GRAPH_MINIMAP_VIEWBOX.y + GRAPH_MINIMAP_VIEWBOX.height / 2 }); assertPointNear(worldPoint, { x: 500, y: 340 }); assert.ok(minimapRect.x > GRAPH_MINIMAP_VIEWBOX.x); assert.ok(minimapRect.y > GRAPH_MINIMAP_VIEWBOX.y); assert.ok(minimapRect.width > 0); assert.ok(minimapRect.height > 0); }); it("derives side anchors for isolated diff motion outside the default world", () => { assert.deepEqual(sideExitWorldAnchor({ x: 120, y: 20 }), { x: -80, y: 80 }); assert.deepEqual(sideExitWorldAnchor({ x: 900, y: 900 }), { x: 1080, y: 600 }); }); });