1
0
Fork 0
Code Issues Pull requests Projects Releases 2 Packages Wiki Activity Actions Pages

Polish sparse graph navigation

This commit is contained in:
Andraxion 2026-07-24 22:54:19 -04:00
parent 5ccb336e84
commit 7834461eb9
8 changed files with 148 additions and 28 deletions

View file

@ -101,7 +101,7 @@ class DocForgeMcpTests(unittest.IsolatedAsyncioTestCase):
visualization = results[11].structuredContent["visualization"]
self.assertTrue(visualization["read_only"])
self.assertTrue(visualization["project_bound"])
self.assertEqual("graph-browser@5", visualization["template"])
self.assertEqual("graph-browser@6", visualization["template"])
self.assertEqual("browser_lease", visualization["lifetime"]["policy"])
self.assertTrue(visualization["url"].startswith("http://127.0.0.1:"))
context = results[8].structuredContent

View file

@ -81,11 +81,17 @@ class VisualizationTests(unittest.TestCase):
@unittest.skipUnless(shutil.which("node"), "Node.js is required for topology validation")
def test_embedded_topology_roles_hops_and_shading_are_deterministic(self) -> None:
script = _GRAPH_BROWSER_HTML.split("<script>", 1)[1].split("</script>", 1)[0]
viewport_logic = script.split("function viewportForPositions", 1)[1].split(
"function selectNode", 1
)[0]
topology_logic = script.split("function analyzeTopology", 1)[1].split(
"function renderNeighborhood", 1
)[0]
harness = (
"function analyzeTopology"
"const defaultViewport = Object.freeze({x: -600, y: -410, width: 1200, height: 820});\n"
"function viewportForPositions"
+ viewport_logic
+ "function analyzeTopology"
+ topology_logic
+ """
const data = {
@ -116,6 +122,18 @@ if (Math.hypot(positions.get("child-two").x, positions.get("child-two").y)
fail("hop rings");
}
if (nodePalette("child", 2).fill === nodePalette("child", 1).fill) fail("hop shading");
const singleViewport = viewportForPositions(new Map([["primary", {x: 0, y: 0}]]));
if (singleViewport.width !== 440) fail("single-node fit");
if (singleViewport.x !== -220 || singleViewport.y !== -singleViewport.height / 2) {
fail("single-node centered");
}
const centered = viewportCenteredOn(
positions.get("child-two"),
{x: 20, y: 30, width: 500, height: 300},
);
if (centered.width !== 500 || centered.height !== 300) fail("center preserves zoom");
if (centered.x !== positions.get("child-two").x - 250) fail("center x");
if (centered.y !== positions.get("child-two").y - 150) fail("center y");
"""
)
result = subprocess.run(
@ -160,6 +178,7 @@ if (nodePalette("child", 2).fill === nodePalette("child", 1).fill) fail("hop sha
self.assertIn('id="right-resizer"', html)
self.assertIn('id="neighborhood-sections"', html)
self.assertIn(".empty[hidden] { display: none; }", html)
self.assertIn("Space centers selection", html)
self.assertIn("resize: both", html)
self.assertNotIn("backdrop-filter", html)
self.assertIn('addEventListener("wheel"', html)
@ -173,6 +192,10 @@ if (nodePalette("child", 2).fill === nodePalette("child", 1).fill) fail("hop sha
self.assertIn("Primary focus", html)
self.assertIn("Edge & context", html)
self.assertIn("distanceShade", html)
self.assertIn("viewportForPositions", html)
self.assertIn("centerSelectedNode", html)
self.assertIn('event.code !== "Space"', html)
self.assertIn('class: "selection-ring"', html)
self.assertIn("renewViewerLease", html)
pointerdown = html.split('$("graph").addEventListener("pointerdown"', 1)[1].split(
'$("graph").addEventListener("pointermove"', 1
@ -182,7 +205,7 @@ if (nodePalette("child", 2).fill === nodePalette("child", 1).fill) fail("hop sha
)[0]
self.assertNotIn("setPointerCapture", pointerdown)
self.assertIn("setPointerCapture", pointermove)
self.assertIn("left-drag to pan", html)
self.assertIn("left-drag pans", html)
self.assertIn("default-src 'none'", headers["Content-Security-Policy"])
self.assertEqual("no-store", headers["Cache-Control"])
self.assertEqual("DENY", headers["X-Frame-Options"])