// Headless guard for the trace-chart viewer's keyboard-control math (keyXRange). // // keyXRange is PURE: given a key + the current x-range [lo,hi] + the zoom anchor (the // x-value to keep fixed, i.e. the cursor position) + the data extent [first,last], it // returns the next {min,max} — or null for a non-chart key. The load-bearing property is // "zoom toward the cursor": the anchor keeps its RELATIVE position in the new range, so // the point under the mouse stays put. A regression that zoomed around the centre instead // would still shrink the range but slide the cursor's point — caught here by an anchor // off-centre (anchor=2 in a [0,10] range). Arrows pan by 15% of the span; 0/Home reset to // the extent. Drives the pure export; no DOM/uPlot. import { createRequire } from "node:module"; import { fileURLToPath } from "node:url"; import { dirname, join } from "node:path"; const require = createRequire(import.meta.url); const here = dirname(fileURLToPath(import.meta.url)); const fail = (msg) => { console.error("FAIL: " + msg); process.exit(1); }; const close = (a, b) => Math.abs(a - b) < 1e-9; const eqRange = (r, min, max, msg) => { if (!r || !close(r.min, min) || !close(r.max, max)) fail(`${msg} (got ${JSON.stringify(r)}, want {min:${min},max:${max}})`); }; // No document / no AURA_TRACES -> the IIFE's browser mount is skipped; we get the export. global.window = {}; const { keyXRange } = require(join(here, "..", "assets", "chart-viewer.js")); // lo=0, hi=10 (span 10), anchor=2 (off-centre, left), full extent [0, 99]. const lo = 0, hi = 10, anchor = 2, ext = [0, 99]; // pan: 15% of span = 1.5, span preserved. eqRange(keyXRange("ArrowLeft", lo, hi, anchor, ext), -1.5, 8.5, "ArrowLeft pans left by 15% of the span"); eqRange(keyXRange("ArrowRight", lo, hi, anchor, ext), 1.5, 11.5, "ArrowRight pans right by 15% of the span"); // zoom in (factor 0.8) toward anchor=2 -> newLo=0.4, newHi=8.4 (span 8). const zin = keyXRange("+", lo, hi, anchor, ext); eqRange(zin, 0.4, 8.4, "+ zooms in toward the anchor"); if (!close((anchor - zin.min) / (zin.max - zin.min), (anchor - lo) / (hi - lo))) fail("+ must keep the anchor at the same relative position (zoom toward cursor, not centre)"); eqRange(keyXRange("=", lo, hi, anchor, ext), 0.4, 8.4, "= aliases + (zoom in)"); // zoom out (factor 1.25) toward anchor=2 -> newLo=-0.5, newHi=12. eqRange(keyXRange("-", lo, hi, anchor, ext), -0.5, 12, "- zooms out toward the anchor"); eqRange(keyXRange("_", lo, hi, anchor, ext), -0.5, 12, "_ aliases - (zoom out)"); // reset to the full data extent. eqRange(keyXRange("0", lo, hi, anchor, ext), 0, 99, "0 resets to the data extent"); eqRange(keyXRange("Home", lo, hi, anchor, ext), 0, 99, "Home resets to the data extent"); // a non-chart key is ignored. if (keyXRange("q", lo, hi, anchor, ext) !== null) fail("a non-chart key must return null"); console.log("OK — keyboard pan / zoom-toward-cursor / reset math; non-chart keys ignored."); process.exit(0);