test(ui): cover rail exclusion and add the panel-resize QA harness

The right-rail test now proves a .panel-floating panel is neither
measured, allocated nor auto-collapsed while its docked sibling still is.
scripts/qa-panel-resize.mjs drives the CCTV panel in a real browser:
lift-out by header drag, all eight handles with the opposite edge pinned,
minimum size, reload restore and double-click snap-back. Its pure helpers
sit behind a main guard so src/tooling/panelResizeQa.test.mjs can check
them without a browser. Run it with `npm run qa:panel-resize`.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
This commit is contained in:
Said TurkseverandClaude Fable 5.1 committed 2026-09-23 15:21:21 +01:00
1 parent 96a1e710ea
commit 335b4b08a2
5 files changed
+368 -1

No files matched your search

+57
View File
@@ -0,0 +1,57 @@
import assert from 'node:assert/strict';
import test from 'node:test';
import {
driftedEdges,
growthDelta,
handlePoint,
MIN_SIZE,
pinnedEdges,
STORAGE_KEY,
} from '../../scripts/qa-panel-resize.mjs';
import { RESIZE_DIRECTIONS } from '../ui/panelResize.js';
test('the harness targets the key and minimums the controls use', () => {
assert.equal(STORAGE_KEY, 'godsEyeView.v8.panelPos.cctv-panel');
assert.deepEqual(MIN_SIZE, { width: 300, height: 160 });
});
test('pinned edges are exactly the sides a direction does not name', () => {
assert.deepEqual(pinnedEdges('n'), ['bottom', 'right', 'left']);
assert.deepEqual(pinnedEdges('se'), ['top', 'left']);
assert.deepEqual(pinnedEdges('nw'), ['bottom', 'right']);
for (const dir of RESIZE_DIRECTIONS)
assert.equal(pinnedEdges(dir).length, 4 - dir.length);
});
test('growth deltas push each named edge outward', () => {
assert.deepEqual(growthDelta('n', 40), { dx: 0, dy: -40 });
assert.deepEqual(growthDelta('e', 40), { dx: 40, dy: 0 });
assert.deepEqual(growthDelta('sw', 40), { dx: -40, dy: 40 });
assert.deepEqual(growthDelta('se', 40), { dx: 40, dy: 40 });
});
test('handle points land inside the strip or corner for every direction', () => {
const rect = { left: 100, top: 200, width: 400, height: 300 };
rect.right = rect.left + rect.width;
rect.bottom = rect.top + rect.height;
assert.deepEqual(handlePoint(rect, 'n'), { x: 300, y: 201 });
assert.deepEqual(handlePoint(rect, 'w'), { x: 101, y: 350 });
assert.deepEqual(handlePoint(rect, 'se'), { x: 496, y: 496 });
assert.deepEqual(handlePoint(rect, 'nw'), { x: 104, y: 204 });
for (const dir of RESIZE_DIRECTIONS) {
const { x, y } = handlePoint(rect, dir);
assert.ok(x >= rect.left && x <= rect.right, `${dir} x inside`);
assert.ok(y >= rect.top && y <= rect.bottom, `${dir} y inside`);
}
});
test('drift detection reports only pinned edges that moved', () => {
const before = { left: 100, top: 100, width: 400, height: 300 };
const grewEast = { left: 100, top: 100, width: 440, height: 300 };
assert.deepEqual(driftedEdges(before, grewEast, 'e'), []);
const slidWest = { left: 60, top: 100, width: 440, height: 300 };
assert.deepEqual(driftedEdges(before, slidWest, 'e'), ['left']);
assert.deepEqual(driftedEdges(before, slidWest, 'w'), []);
const wobble = { left: 101, top: 100, width: 439, height: 300 };
assert.deepEqual(driftedEdges(before, wobble, 'e'), []);
});
+22
View File
@@ -243,6 +243,28 @@ test('right layout retains Display allocation during measurement and caps restor
);
});
test('right rail neither measures nor allocates a panel floating out of it', () => {
const f = fixture('right');
f.expand(f.first, 900);
f.expand(f.second, 300);
f.first.classList.add('panel-floating');
f.first.style.setProperty('--right-panel-allocated-height', '500px');
f.first.writes.length = 0;
f.run();
assert.equal(f.stack.dataset.expandedCount, '1');
assert.deepEqual(
f.first.writes,
[],
'a floating panel keeps whatever inline style it left the rail with',
);
assert.equal(f.first.getAttribute('aria-hidden'), undefined);
assert.equal(f.first.classList.contains('layout-auto-collapsed'), false);
assert.ok(
f.second.style.getPropertyValue('--right-panel-allocated-height'),
'the docked panel still receives its allocation',
);
});
test('right rail aligns to the current left rail and excludes hidden obstacles', () => {
const f = fixture('right');
f.options.leftStack.rect.top = 200;