mirror of
https://github.com/Kpa-clawbot/meshcore-analyzer.git
synced 2026-08-29 04:38:20 +00:00
## Summary Fixes the move-panel corner-cycle button silently no-op'ing after a panel is dragged on `/live`. Two coexisting positioning systems were mutating disjoint state: - `public/drag-manager.js` sets inline `top/left/right/bottom/transform/position`, stamps `data-dragged="true"`, and persists `localStorage['panel-drag-<id>']`. - `public/live.js` `applyPanelPosition()` only flips the `data-position` attribute (selecting a `.live-overlay[data-position="…"]` rule with `top/left/right/bottom`). Inline styles win the cascade, so after any drag the corner button updated the glyph but the panel never moved. The fix has `onCornerClick` clear drag state (attribute, inline coords, localStorage) before calling `applyPanelPosition`. ## Commits - Red: `ea2f8009` — `test(live): failing E2E for corner-cycle button after drag (#1567)` — Playwright test injects DragManager-shaped drag state on `#liveFeed`, clicks `.panel-corner-btn`, asserts `data-dragged`/inline styles/`localStorage` are cleared AND `getBoundingClientRect()` matches the CSS corner anchor (not the dragged coords). Fails on master at the post-click assertion. - Green: `abb5a21f` — `fix(live): corner-cycle button clears drag state (#1567)` — 11-line change in `onCornerClick`, plus new E2E wired into the workflow. ## Files - `public/live.js` — `onCornerClick` clears `data-dragged`, inline `top/left/right/bottom/transform/position`, and `localStorage['panel-drag-<id>']` before `applyPanelPosition`. - `test-issue-1567-corner-clears-drag-e2e.js` — new Playwright E2E (drag-state injection + post-click rect assertion). - `.github/workflows/deploy.yml` — runs the new E2E next to `test-drag-manager-e2e.js`. ## E2E E2E assertion added: `test-issue-1567-corner-clears-drag-e2e.js:108` (post-click drag-state + anchor-match assertions). Browser verified: red-on-master gated by assertion (`'data-dragged must be cleared after corner click'`) — green commit makes it pass. ## Scope - No changes to `drag-manager.js` (out of scope per triage fix path). - No config / API surface changes. - Desktop drag path only; mobile / coarse-pointer path unchanged (drag is gated off there at `live.js:1941`, so the button was always the only repositioning affordance on touch — preserved). Partial fix for #1567 — addresses the corner-button-no-op symptom called out in triage; leaves the issue open for the user to verify in the browser and close. --------- Co-authored-by: Kpa-clawbot <bot@openclaw.local> Co-authored-by: mc-bot <bot@meshcore.local>
This commit is contained in:
co-authored by
Kpa-clawbot
mc-bot
parent
5fa6568835
commit
2b45f7872c
@@ -406,6 +406,7 @@ jobs:
|
|||||||
BASE_URL=http://localhost:13581 node test-customize-display-e2e.js 2>&1 | tee -a e2e-output.txt
|
BASE_URL=http://localhost:13581 node test-customize-display-e2e.js 2>&1 | tee -a e2e-output.txt
|
||||||
BASE_URL=http://localhost:13581 node test-customize-export-e2e.js 2>&1 | tee -a e2e-output.txt
|
BASE_URL=http://localhost:13581 node test-customize-export-e2e.js 2>&1 | tee -a e2e-output.txt
|
||||||
CHROMIUM_REQUIRE=1 BASE_URL=http://localhost:13581 node test-drag-manager-e2e.js 2>&1 | tee -a e2e-output.txt
|
CHROMIUM_REQUIRE=1 BASE_URL=http://localhost:13581 node test-drag-manager-e2e.js 2>&1 | tee -a e2e-output.txt
|
||||||
|
CHROMIUM_REQUIRE=1 BASE_URL=http://localhost:13581 node test-issue-1567-corner-clears-drag-e2e.js 2>&1 | tee -a e2e-output.txt
|
||||||
CHROMIUM_REQUIRE=1 BASE_URL=http://localhost:13581 node test-issue-1306-collisions-terminology-e2e.js 2>&1 | tee -a e2e-output.txt
|
CHROMIUM_REQUIRE=1 BASE_URL=http://localhost:13581 node test-issue-1306-collisions-terminology-e2e.js 2>&1 | tee -a e2e-output.txt
|
||||||
CHROMIUM_REQUIRE=1 BASE_URL=http://localhost:13581 node test-issue-1374-route-map-a11y-e2e.js 2>&1 | tee -a e2e-output.txt
|
CHROMIUM_REQUIRE=1 BASE_URL=http://localhost:13581 node test-issue-1374-route-map-a11y-e2e.js 2>&1 | tee -a e2e-output.txt
|
||||||
CHROMIUM_REQUIRE=1 BASE_URL=http://localhost:13581 node test-channels-list-render-e2e.js 2>&1 | tee -a e2e-output.txt
|
CHROMIUM_REQUIRE=1 BASE_URL=http://localhost:13581 node test-channels-list-render-e2e.js 2>&1 | tee -a e2e-output.txt
|
||||||
|
|||||||
+34
-13
@@ -10,6 +10,29 @@
|
|||||||
var SNAP_THRESHOLD = 20; // px — snap to edge on release
|
var SNAP_THRESHOLD = 20; // px — snap to edge on release
|
||||||
var SNAP_MARGIN = 12; // px — margin when snapped
|
var SNAP_MARGIN = 12; // px — margin when snapped
|
||||||
|
|
||||||
|
// Shared drag-state cleaner (#1568 round-1 MAJOR 2).
|
||||||
|
// Removes every side-effect _detachFromCorner/_finalizePosition/_persist
|
||||||
|
// can leave on a panel element so subsequent corner CSS rules win the
|
||||||
|
// cascade. Call sites: Escape revert, responsive gate, panel corner-
|
||||||
|
// click, reset-to-defaults. Keep this list in sync with _detachFromCorner.
|
||||||
|
// - removes `data-dragged` attribute
|
||||||
|
// - removes `is-dragging` class (defensive: normally cleared on
|
||||||
|
// pointerup, but cheap symmetry for races)
|
||||||
|
// - clears inline top/left/right/bottom/transform/position/zIndex
|
||||||
|
// (zIndex is bumped on every DRAGGING transition; must reset)
|
||||||
|
// - removes `panel-drag-<id>` from localStorage when clearStorage !== false
|
||||||
|
function clearPanel(el, panelId, opts) {
|
||||||
|
if (!el) return;
|
||||||
|
el.removeAttribute('data-dragged');
|
||||||
|
el.classList.remove('is-dragging');
|
||||||
|
['top', 'left', 'right', 'bottom', 'transform', 'position', 'zIndex'].forEach(function (p) {
|
||||||
|
el.style[p] = '';
|
||||||
|
});
|
||||||
|
if (!opts || opts.clearStorage !== false) {
|
||||||
|
try { localStorage.removeItem('panel-drag-' + panelId); } catch (_) { /* ignore */ }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function DragManager() {
|
function DragManager() {
|
||||||
this.state = 'IDLE';
|
this.state = 'IDLE';
|
||||||
this.activePanel = null;
|
this.activePanel = null;
|
||||||
@@ -89,24 +112,21 @@
|
|||||||
|
|
||||||
DragManager.prototype._handleKeyDown = function (e) {
|
DragManager.prototype._handleKeyDown = function (e) {
|
||||||
if (e.key === 'Escape' && this.state === 'DRAGGING' && this.activePanel) {
|
if (e.key === 'Escape' && this.state === 'DRAGGING' && this.activePanel) {
|
||||||
this.activePanel.classList.remove('is-dragging');
|
var panel = this.activePanel;
|
||||||
this.activePanel.style.transform = this.preTransform;
|
panel.style.transform = this.preTransform;
|
||||||
// Revert: re-attach to corner if it was cornered before
|
// Revert: re-attach to corner if it was cornered before
|
||||||
var saved = localStorage.getItem('panel-drag-' + this.activePanel.id);
|
var saved = localStorage.getItem('panel-drag-' + panel.id);
|
||||||
if (!saved) {
|
if (!saved) {
|
||||||
// Was in corner mode — restore corner CSS
|
// Was in corner mode — restore corner CSS via shared helper.
|
||||||
delete this.activePanel.dataset.dragged;
|
// No storage key existed, so clearStorage is a no-op.
|
||||||
this.activePanel.style.top = '';
|
DragManager.clearPanel(panel, panel.id);
|
||||||
this.activePanel.style.left = '';
|
|
||||||
this.activePanel.style.right = '';
|
|
||||||
this.activePanel.style.bottom = '';
|
|
||||||
this.activePanel.style.transform = '';
|
|
||||||
// Re-apply corner position from M0
|
// Re-apply corner position from M0
|
||||||
var corner = localStorage.getItem('panel-corner-' + this.activePanel.id);
|
var corner = localStorage.getItem('panel-corner-' + panel.id);
|
||||||
if (corner) this.activePanel.setAttribute('data-position', corner);
|
if (corner) panel.setAttribute('data-position', corner);
|
||||||
} else {
|
} else {
|
||||||
// Was already dragged — revert to pre-drag position
|
// Was already dragged — revert to pre-drag position
|
||||||
this.activePanel.style.transform = 'none';
|
panel.classList.remove('is-dragging');
|
||||||
|
panel.style.transform = 'none';
|
||||||
}
|
}
|
||||||
this._reset();
|
this._reset();
|
||||||
}
|
}
|
||||||
@@ -213,4 +233,5 @@
|
|||||||
|
|
||||||
// Export
|
// Export
|
||||||
window.DragManager = DragManager;
|
window.DragManager = DragManager;
|
||||||
|
DragManager.clearPanel = clearPanel;
|
||||||
})();
|
})();
|
||||||
|
|||||||
+24
-7
@@ -215,6 +215,14 @@
|
|||||||
var nextIdx = (CORNER_CYCLE.indexOf(current) + 1) % 4;
|
var nextIdx = (CORNER_CYCLE.indexOf(current) + 1) % 4;
|
||||||
var next = nextAvailableCorner(panelId, CORNER_CYCLE[nextIdx], positions);
|
var next = nextAvailableCorner(panelId, CORNER_CYCLE[nextIdx], positions);
|
||||||
try { localStorage.setItem('panel-corner-' + panelId, next); } catch (_) { /* quota */ }
|
try { localStorage.setItem('panel-corner-' + panelId, next); } catch (_) { /* quota */ }
|
||||||
|
// #1567: corner button must clear any prior free-form drag state, or
|
||||||
|
// the inline top/left from drag-manager.js wins the cascade over the
|
||||||
|
// corner anchors and the panel silently no-ops on click.
|
||||||
|
// #1568 round-1 MAJOR 2: shared cleaner — keeps Escape revert,
|
||||||
|
// responsive gate, corner-click, and reset paths in sync.
|
||||||
|
if (window.DragManager && DragManager.clearPanel) {
|
||||||
|
DragManager.clearPanel(document.getElementById(panelId), panelId);
|
||||||
|
}
|
||||||
applyPanelPosition(panelId, next);
|
applyPanelPosition(panelId, next);
|
||||||
// Announce for screen readers
|
// Announce for screen readers
|
||||||
var announce = document.getElementById('panelPositionAnnounce');
|
var announce = document.getElementById('panelPositionAnnounce');
|
||||||
@@ -224,6 +232,11 @@
|
|||||||
function resetPanelPositions() {
|
function resetPanelPositions() {
|
||||||
for (var id in PANEL_DEFAULTS) {
|
for (var id in PANEL_DEFAULTS) {
|
||||||
try { localStorage.removeItem('panel-corner-' + id); } catch (_) { /* ignore */ }
|
try { localStorage.removeItem('panel-corner-' + id); } catch (_) { /* ignore */ }
|
||||||
|
// #1568 round-1 MAJOR 1: clear drag state before applying defaults,
|
||||||
|
// otherwise a dragged panel's inline coords win the cascade.
|
||||||
|
if (window.DragManager && DragManager.clearPanel) {
|
||||||
|
DragManager.clearPanel(document.getElementById(id), id);
|
||||||
|
}
|
||||||
applyPanelPosition(id, PANEL_DEFAULTS[id]);
|
applyPanelPosition(id, PANEL_DEFAULTS[id]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -326,6 +339,12 @@
|
|||||||
function publish() {
|
function publish() {
|
||||||
var h = Math.ceil(bar.getBoundingClientRect().height) || 58;
|
var h = Math.ceil(bar.getBoundingClientRect().height) || 58;
|
||||||
page.style.setProperty('--vcr-bar-height', h + 'px');
|
page.style.setProperty('--vcr-bar-height', h + 'px');
|
||||||
|
// #1568 round-2: also publish on :root so JS reading
|
||||||
|
// getComputedStyle(document.documentElement).getPropertyValue(
|
||||||
|
// '--vcr-bar-height') sees the measured value (E2E assertions,
|
||||||
|
// future global consumers). CSS resolution unchanged — the
|
||||||
|
// .live-page-scoped var still wins for live-overlay rules.
|
||||||
|
try { document.documentElement.style.setProperty('--vcr-bar-height', h + 'px'); } catch (_) {}
|
||||||
}
|
}
|
||||||
publish();
|
publish();
|
||||||
var ro = null;
|
var ro = null;
|
||||||
@@ -1941,14 +1960,12 @@
|
|||||||
var dragMql = window.matchMedia('(pointer: fine) and (min-width: 768px)');
|
var dragMql = window.matchMedia('(pointer: fine) and (min-width: 768px)');
|
||||||
function onDragMediaChange(e) {
|
function onDragMediaChange(e) {
|
||||||
if (!e.matches) {
|
if (!e.matches) {
|
||||||
// Revert dragged panels to corner positions
|
// Revert dragged panels to corner positions. Preserve the
|
||||||
|
// localStorage drag key so widening the viewport restores
|
||||||
|
// the dragged position via dragMgr.restorePositions().
|
||||||
|
// #1568 round-1 MAJOR 2: shared cleaner with clearStorage:false.
|
||||||
document.querySelectorAll('.live-overlay[data-dragged="true"]').forEach(function (p) {
|
document.querySelectorAll('.live-overlay[data-dragged="true"]').forEach(function (p) {
|
||||||
delete p.dataset.dragged;
|
DragManager.clearPanel(p, p.id, { clearStorage: false });
|
||||||
p.style.transform = '';
|
|
||||||
p.style.top = '';
|
|
||||||
p.style.left = '';
|
|
||||||
p.style.right = '';
|
|
||||||
p.style.bottom = '';
|
|
||||||
});
|
});
|
||||||
initPanelPositions();
|
initPanelPositions();
|
||||||
dragMgr.disable();
|
dragMgr.disable();
|
||||||
|
|||||||
@@ -0,0 +1,250 @@
|
|||||||
|
/**
|
||||||
|
* E2E for #1567 — Move-panel corner-cycle button silently no-ops after the
|
||||||
|
* panel has been dragged.
|
||||||
|
*
|
||||||
|
* Root cause (see triage on #1567): two coexisting positioning systems
|
||||||
|
* mutate disjoint state. `drag-manager.js` sets `data-dragged="true"` and
|
||||||
|
* inline `position:fixed; top/left/right:auto; bottom:auto; transform:none`
|
||||||
|
* and persists to `localStorage['panel-drag-<id>']`. `live.js` →
|
||||||
|
* `applyPanelPosition()` only flips the `data-position` attribute (which
|
||||||
|
* selects a `.live-overlay[data-position="…"]` CSS rule with `top/left/right/
|
||||||
|
* bottom`). The inline styles win cascade-wise, so the panel does not move.
|
||||||
|
*
|
||||||
|
* Fix path under test: `onCornerClick` must clear drag state (attribute,
|
||||||
|
* inline coords, localStorage) BEFORE calling `applyPanelPosition`.
|
||||||
|
*
|
||||||
|
* Assertions:
|
||||||
|
* (a) After programmatic drag, the panel sits at the dragged coords
|
||||||
|
* (sanity; if false the harness is broken).
|
||||||
|
* (b) After clicking the panel-corner-btn, `data-dragged` is gone,
|
||||||
|
* inline `top/left/right/bottom/transform/position` are cleared,
|
||||||
|
* `localStorage['panel-drag-<id>']` is gone, and the panel's
|
||||||
|
* bounding rect matches the CSS corner-anchor for the new
|
||||||
|
* `data-position` value (NOT the dragged coords).
|
||||||
|
*
|
||||||
|
* Red-on-master: assertion (b) fails on master — panel stays at the
|
||||||
|
* dragged coords after the click because inline styles are not cleared.
|
||||||
|
*
|
||||||
|
* Run: BASE_URL=http://localhost:13581 node test-issue-1567-corner-clears-drag-e2e.js
|
||||||
|
*/
|
||||||
|
'use strict';
|
||||||
|
const { chromium } = require('playwright');
|
||||||
|
|
||||||
|
const BASE = process.env.BASE_URL || 'http://localhost:3000';
|
||||||
|
|
||||||
|
let passed = 0, failed = 0;
|
||||||
|
async function step(name, fn) {
|
||||||
|
try { await fn(); passed++; console.log(' \u2713 ' + name); }
|
||||||
|
catch (e) { failed++; console.error(' \u2717 ' + name + ': ' + e.message); }
|
||||||
|
}
|
||||||
|
function assert(c, m) { if (!c) throw new Error(m || 'assertion failed'); }
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
const browser = await chromium.launch({
|
||||||
|
headless: true,
|
||||||
|
executablePath: process.env.CHROMIUM_PATH || undefined,
|
||||||
|
args: ['--no-sandbox', '--disable-gpu', '--disable-dev-shm-usage'],
|
||||||
|
});
|
||||||
|
// Desktop viewport — drag is gated off on coarse pointer / narrow widths.
|
||||||
|
const ctx = await browser.newContext({ viewport: { width: 1280, height: 900 } });
|
||||||
|
const page = await ctx.newPage();
|
||||||
|
page.setDefaultTimeout(10000);
|
||||||
|
page.on('pageerror', (e) => console.error('[pageerror]', e.message));
|
||||||
|
|
||||||
|
console.log(`\n=== #1567 corner-button clears drag state E2E against ${BASE} ===`);
|
||||||
|
|
||||||
|
await step('navigate to /live with a clean slate', async () => {
|
||||||
|
await page.goto(BASE + '/#/live', { waitUntil: 'domcontentloaded' });
|
||||||
|
await page.evaluate(() => {
|
||||||
|
['liveFeed', 'liveLegend', 'liveNodeDetail'].forEach((id) => {
|
||||||
|
try { localStorage.removeItem('panel-drag-' + id); } catch (_) {}
|
||||||
|
try { localStorage.removeItem('panel-corner-' + id); } catch (_) {}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
await page.reload({ waitUntil: 'load' });
|
||||||
|
await page.waitForSelector('#liveFeed .panel-corner-btn', { timeout: 8000 });
|
||||||
|
await page.waitForTimeout(200);
|
||||||
|
});
|
||||||
|
|
||||||
|
// (a) Programmatically push #liveFeed into "dragged" state via the same
|
||||||
|
// DOM shape DragManager produces. Using direct attribute/style writes
|
||||||
|
// keeps the test deterministic across viewports / pointer types and
|
||||||
|
// avoids racing the real drag handlers.
|
||||||
|
await step('inject drag state on #liveFeed (mirrors DragManager output)', async () => {
|
||||||
|
const got = await page.evaluate(() => {
|
||||||
|
const el = document.getElementById('liveFeed');
|
||||||
|
if (!el) return null;
|
||||||
|
el.removeAttribute('data-position');
|
||||||
|
el.dataset.dragged = 'true';
|
||||||
|
el.style.position = 'fixed';
|
||||||
|
el.style.top = '300px';
|
||||||
|
el.style.left = '500px';
|
||||||
|
el.style.right = 'auto';
|
||||||
|
el.style.bottom = 'auto';
|
||||||
|
el.style.transform = 'none';
|
||||||
|
localStorage.setItem('panel-drag-liveFeed', JSON.stringify({
|
||||||
|
xPct: 500 / window.innerWidth, yPct: 300 / window.innerHeight,
|
||||||
|
}));
|
||||||
|
const r = el.getBoundingClientRect();
|
||||||
|
return { top: r.top, left: r.left, dragged: el.dataset.dragged };
|
||||||
|
});
|
||||||
|
assert(got, '#liveFeed missing');
|
||||||
|
assert(got.dragged === 'true', 'data-dragged should be "true"');
|
||||||
|
// Tolerate a few px (panel chrome/scrollbar). Bug repro requires the
|
||||||
|
// panel to actually be at the dragged coords before the click.
|
||||||
|
assert(Math.abs(got.top - 300) < 8, 'pre-click panel top ~300, got ' + got.top);
|
||||||
|
assert(Math.abs(got.left - 500) < 8, 'pre-click panel left ~500, got ' + got.left);
|
||||||
|
});
|
||||||
|
|
||||||
|
// (b) Click the corner button and assert drag state is FULLY cleared
|
||||||
|
// and the panel actually sits at the CSS corner anchor for the new
|
||||||
|
// data-position.
|
||||||
|
await step('clicking .panel-corner-btn clears drag state and snaps to the corner anchor', async () => {
|
||||||
|
await page.click('#liveFeed .panel-corner-btn');
|
||||||
|
await page.waitForTimeout(150);
|
||||||
|
|
||||||
|
const after = await page.evaluate(() => {
|
||||||
|
const el = document.getElementById('liveFeed');
|
||||||
|
const pos = el.getAttribute('data-position');
|
||||||
|
const rect = el.getBoundingClientRect();
|
||||||
|
// Compute the CSS-rule anchor for this corner. Mirrors live.css
|
||||||
|
// .live-overlay[data-position="…"] rules (see public/live.css ~1200).
|
||||||
|
const VCR = parseInt(getComputedStyle(document.documentElement)
|
||||||
|
.getPropertyValue('--vcr-bar-height')) || 58;
|
||||||
|
const anchors = {
|
||||||
|
tl: { top: 64, left: 12 },
|
||||||
|
tr: { top: 64, right: 12 },
|
||||||
|
bl: { bottom: VCR + 10, left: 12 },
|
||||||
|
br: { bottom: VCR + 10, right: 12 },
|
||||||
|
};
|
||||||
|
return {
|
||||||
|
dragged: el.dataset.dragged,
|
||||||
|
inlineTop: el.style.top,
|
||||||
|
inlineLeft: el.style.left,
|
||||||
|
inlineRight: el.style.right,
|
||||||
|
inlineBottom: el.style.bottom,
|
||||||
|
inlineTransform: el.style.transform,
|
||||||
|
inlinePosition: el.style.position,
|
||||||
|
ls: localStorage.getItem('panel-drag-liveFeed'),
|
||||||
|
pos: pos,
|
||||||
|
rect: { top: rect.top, left: rect.left, right: rect.right, bottom: rect.bottom },
|
||||||
|
vw: window.innerWidth, vh: window.innerHeight,
|
||||||
|
anchor: anchors[pos],
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
assert(after.pos && /^(tl|tr|bl|br)$/.test(after.pos),
|
||||||
|
'data-position must be a corner code, got: ' + after.pos);
|
||||||
|
assert(!after.dragged,
|
||||||
|
'data-dragged must be cleared after corner click, got: ' + after.dragged);
|
||||||
|
assert(!after.ls,
|
||||||
|
'localStorage panel-drag-liveFeed must be removed, got: ' + after.ls);
|
||||||
|
['inlineTop', 'inlineLeft', 'inlineRight', 'inlineBottom', 'inlineTransform', 'inlinePosition'].forEach((k) => {
|
||||||
|
assert(after[k] === '',
|
||||||
|
'inline style ' + k + ' must be cleared after corner click, got: ' + JSON.stringify(after[k]));
|
||||||
|
});
|
||||||
|
|
||||||
|
// Now the panel must actually be at the corner anchor — not at the
|
||||||
|
// dragged coords. Tolerate a few px (border/scrollbar).
|
||||||
|
var TOL = 12;
|
||||||
|
if ('top' in after.anchor) {
|
||||||
|
assert(Math.abs(after.rect.top - after.anchor.top) < TOL,
|
||||||
|
'panel top must match CSS anchor (' + after.anchor.top + ') for ' + after.pos +
|
||||||
|
', got rect.top=' + after.rect.top);
|
||||||
|
}
|
||||||
|
if ('left' in after.anchor) {
|
||||||
|
assert(Math.abs(after.rect.left - after.anchor.left) < TOL,
|
||||||
|
'panel left must match CSS anchor (' + after.anchor.left + ') for ' + after.pos +
|
||||||
|
', got rect.left=' + after.rect.left);
|
||||||
|
}
|
||||||
|
if ('right' in after.anchor) {
|
||||||
|
var rightGap = after.vw - after.rect.right;
|
||||||
|
assert(Math.abs(rightGap - after.anchor.right) < TOL,
|
||||||
|
'panel right-gap must match CSS anchor (' + after.anchor.right + ') for ' + after.pos +
|
||||||
|
', got vw-rect.right=' + rightGap);
|
||||||
|
}
|
||||||
|
if ('bottom' in after.anchor) {
|
||||||
|
var bottomGap = after.vh - after.rect.bottom;
|
||||||
|
assert(Math.abs(bottomGap - after.anchor.bottom) < TOL,
|
||||||
|
'panel bottom-gap must match CSS anchor (' + after.anchor.bottom + ') for ' + after.pos +
|
||||||
|
', got vh-rect.bottom=' + bottomGap);
|
||||||
|
}
|
||||||
|
// Sanity: the panel must have actually moved away from the dragged coords.
|
||||||
|
assert(Math.abs(after.rect.top - 300) > TOL || Math.abs(after.rect.left - 500) > TOL,
|
||||||
|
'panel must move away from dragged coords (300,500); rect=' + JSON.stringify(after.rect));
|
||||||
|
});
|
||||||
|
|
||||||
|
// (c) Same bug applies to `resetPanelPositions` — it calls
|
||||||
|
// applyPanelPosition without clearing drag state, so a dragged panel
|
||||||
|
// won't return to its default corner. Re-inject drag state, invoke
|
||||||
|
// window._panelCorner.resetPanelPositions(), assert full reset.
|
||||||
|
await step('resetPanelPositions clears drag state and snaps to default corner', async () => {
|
||||||
|
const after = await page.evaluate(() => {
|
||||||
|
const el = document.getElementById('liveFeed');
|
||||||
|
// Re-inject drag state (mirrors DragManager output).
|
||||||
|
el.removeAttribute('data-position');
|
||||||
|
el.dataset.dragged = 'true';
|
||||||
|
el.classList.add('is-dragging');
|
||||||
|
el.style.position = 'fixed';
|
||||||
|
el.style.top = '320px';
|
||||||
|
el.style.left = '520px';
|
||||||
|
el.style.right = 'auto';
|
||||||
|
el.style.bottom = 'auto';
|
||||||
|
el.style.transform = 'translate(5px,5px)';
|
||||||
|
el.style.zIndex = '1500';
|
||||||
|
localStorage.setItem('panel-drag-liveFeed', JSON.stringify({
|
||||||
|
xPct: 520 / window.innerWidth, yPct: 320 / window.innerHeight,
|
||||||
|
}));
|
||||||
|
// Now reset.
|
||||||
|
window._panelCorner.resetPanelPositions();
|
||||||
|
const r = el.getBoundingClientRect();
|
||||||
|
const VCR = parseInt(getComputedStyle(document.documentElement)
|
||||||
|
.getPropertyValue('--vcr-bar-height')) || 58;
|
||||||
|
// PANEL_DEFAULTS.liveFeed = 'bl' → anchor bottom: VCR+10, left: 12
|
||||||
|
return {
|
||||||
|
pos: el.getAttribute('data-position'),
|
||||||
|
dragged: el.dataset.dragged,
|
||||||
|
isDragging: el.classList.contains('is-dragging'),
|
||||||
|
inlineTop: el.style.top, inlineLeft: el.style.left,
|
||||||
|
inlineRight: el.style.right, inlineBottom: el.style.bottom,
|
||||||
|
inlineTransform: el.style.transform, inlinePosition: el.style.position,
|
||||||
|
inlineZIndex: el.style.zIndex,
|
||||||
|
ls: localStorage.getItem('panel-drag-liveFeed'),
|
||||||
|
rect: { top: r.top, left: r.left, right: r.right, bottom: r.bottom },
|
||||||
|
vw: window.innerWidth, vh: window.innerHeight,
|
||||||
|
anchor: { bottom: VCR + 10, left: 12 },
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
assert(after.pos === 'bl',
|
||||||
|
'data-position must reset to default "bl" for liveFeed, got: ' + after.pos);
|
||||||
|
assert(!after.dragged,
|
||||||
|
'data-dragged must be cleared after reset, got: ' + after.dragged);
|
||||||
|
assert(!after.isDragging,
|
||||||
|
'is-dragging class must be cleared after reset');
|
||||||
|
assert(!after.ls,
|
||||||
|
'localStorage panel-drag-liveFeed must be removed after reset, got: ' + after.ls);
|
||||||
|
assert(after.inlineZIndex === '',
|
||||||
|
'inline zIndex must be cleared after reset, got: ' + JSON.stringify(after.inlineZIndex));
|
||||||
|
['inlineTop', 'inlineLeft', 'inlineRight', 'inlineBottom', 'inlineTransform', 'inlinePosition'].forEach(function (k) {
|
||||||
|
assert(after[k] === '',
|
||||||
|
'inline style ' + k + ' must be cleared after reset, got: ' + JSON.stringify(after[k]));
|
||||||
|
});
|
||||||
|
var TOL = 12;
|
||||||
|
var bottomGap = after.vh - after.rect.bottom;
|
||||||
|
assert(Math.abs(bottomGap - after.anchor.bottom) < TOL,
|
||||||
|
'panel bottom-gap must match default-corner anchor (' + after.anchor.bottom +
|
||||||
|
'), got vh-rect.bottom=' + bottomGap);
|
||||||
|
assert(Math.abs(after.rect.left - after.anchor.left) < TOL,
|
||||||
|
'panel left must match default-corner anchor (' + after.anchor.left +
|
||||||
|
'), got rect.left=' + after.rect.left);
|
||||||
|
// Sanity: panel must have moved off the injected dragged coords.
|
||||||
|
assert(Math.abs(after.rect.top - 320) > TOL || Math.abs(after.rect.left - 520) > TOL,
|
||||||
|
'panel must move away from injected dragged coords (320,520); rect=' + JSON.stringify(after.rect));
|
||||||
|
});
|
||||||
|
|
||||||
|
await ctx.close();
|
||||||
|
await browser.close();
|
||||||
|
console.log(`\n=== Results: ${passed} passed, ${failed} failed ===`);
|
||||||
|
process.exit(failed > 0 ? 1 : 0);
|
||||||
|
})().catch(e => { console.error(e); process.exit(1); });
|
||||||
Reference in New Issue
Block a user