// Programmatic test for the synthesized window resize's frame arithmetic. // // bun scripts/resize-math-probe.ts // // Why this exists: the win7 skin resizes itself by dragging handles and moving // the native frame (see ui/src/app/skins/7like/window-resize/window-resize.ts — // a STOPGAP with a removal checklist). Pointer drags cannot be synthesized // headlessly on this machine, so the interaction itself needs a human; but the // part most likely to be subtly wrong is pure arithmetic — eight edges, four of // which move the window's ORIGIN as well as its size, plus a minimum-size clamp // that must not let the anchored edge drift. That is all in `resize-math.ts`, // which is deliberately dependency-free so this script can drive it directly. // // The failure this guards against is not "the window does not resize" (obvious // on sight) but "dragging the west edge inward past the minimum walks the whole // window across the screen" — correct-looking until you hit the limit. // // This covers MORE than the UI currently mounts, deliberately: only the // bottom-right grip is wired up (an invisible handle over a caption button // steals its hover — see the component), but the math stays general so that // restoring a side later is one line against already-proven arithmetic. import { MIN_HEIGHT, MIN_WIDTH, RESIZE_EDGES, type Frame, type ResizeEdge, resizeFrame, } from "../ui/src/app/skins/7like/window-resize/resize-math"; const START: Frame = { x: 100, y: 200, width: 1000, height: 800 }; interface Case { name: string; edge: ResizeEdge; dx: number; dy: number; want: Frame; } // Every expectation is written out in full rather than computed, so a bug in // resizeFrame cannot be mirrored into the expectation by sharing its algebra. const CASES: Case[] = [ // --- edges: each moves only what it owns --- { name: "east grows width only", edge: "e", dx: 50, dy: 999, want: { x: 100, y: 200, width: 1050, height: 800 } }, { name: "south grows height only", edge: "s", dx: 999, dy: 40, want: { x: 100, y: 200, width: 1000, height: 840 } }, { name: "west moves x AND width", edge: "w", dx: -30, dy: 0, want: { x: 70, y: 200, width: 1030, height: 800 } }, { name: "north moves y AND height", edge: "n", dx: 0, dy: -25, want: { x: 100, y: 175, width: 1000, height: 825 } }, // --- corners: two axes at once --- { name: "south-east", edge: "se", dx: 10, dy: 20, want: { x: 100, y: 200, width: 1010, height: 820 } }, { name: "north-west", edge: "nw", dx: -10, dy: -20, want: { x: 90, y: 180, width: 1010, height: 820 } }, { name: "north-east", edge: "ne", dx: 10, dy: -20, want: { x: 100, y: 180, width: 1010, height: 820 } }, { name: "south-west", edge: "sw", dx: -10, dy: 20, want: { x: 90, y: 200, width: 1010, height: 820 } }, // --- the anchored edge must not drift once clamped --- // west dragged far east: width parks at MIN, and x parks where the EAST edge // (100 + 1000 = 1100) minus MIN puts it. If x kept tracking dx, the window // would slide away while appearing frozen. { name: "west clamps, east edge holds", edge: "w", dx: 5000, dy: 0, want: { x: 1100 - MIN_WIDTH, y: 200, width: MIN_WIDTH, height: 800 }, }, { name: "north clamps, south edge holds", edge: "n", dx: 0, dy: 5000, want: { x: 100, y: 1000 - MIN_HEIGHT, width: 1000, height: MIN_HEIGHT }, }, // east/south clamp the size but never touch the origin { name: "east clamps, origin holds", edge: "e", dx: -5000, dy: 0, want: { x: 100, y: 200, width: MIN_WIDTH, height: 800 } }, { name: "south clamps, origin holds", edge: "s", dx: 0, dy: -5000, want: { x: 100, y: 200, width: 1000, height: MIN_HEIGHT } }, // a corner clamping on BOTH axes at once { name: "north-west clamps both", edge: "nw", dx: 5000, dy: 5000, want: { x: 1100 - MIN_WIDTH, y: 1000 - MIN_HEIGHT, width: MIN_WIDTH, height: MIN_HEIGHT }, }, // a zero drag must be a no-op, or a click on a handle would nudge the window { name: "zero drag is identity", edge: "se", dx: 0, dy: 0, want: START }, ]; const fmt = (f: Frame) => `${f.x},${f.y} ${f.width}x${f.height}`; console.log("[resize-math] frame arithmetic:\n"); console.log(" case edge got verdict"); let failed = 0; for (const c of CASES) { const got = resizeFrame(c.edge, START, c.dx, c.dy); const ok = fmt(got) === fmt(c.want); if (!ok) failed++; console.log( ` ${c.name.padEnd(32)} ${c.edge.padEnd(5)} ${fmt(got).padEnd(22)} ${ok ? "ok" : `FAIL: want ${fmt(c.want)}`}`, ); } // Structural check: every edge must be exercised above, so adding a ninth // handle without a case cannot slip through. const covered = new Set(CASES.map((c) => c.edge)); const missing = RESIZE_EDGES.filter((e) => !covered.has(e)); if (missing.length) { console.log(`\n[resize-math] no case covers: ${missing.join(", ")}`); failed++; } // The clamp must never produce a frame smaller than the minimum, whatever the // drag — a cheap sweep over every edge and a range of deltas. for (const edge of RESIZE_EDGES) { for (const d of [-9999, -1000, -1, 0, 1, 1000, 9999]) { const got = resizeFrame(edge, START, d, d); if (got.width < MIN_WIDTH || got.height < MIN_HEIGHT) { console.log(`\n[resize-math] ${edge} at delta ${d} produced ${fmt(got)} — under the minimum`); failed++; } } } if (failed) { console.log(`\n[resize-math] ${failed} failure(s)`); process.exit(2); } console.log("\n[resize-math] clean");