Tuning across a band edge moved the whole page under the cursor: the band plan strip is in flow, so a range with no allocations collapsed it from 18px to nothing and dragged every element below it up (measured at 1600x950: overview top 118 to 100, footer 1026 to 1008). It now keeps its height whenever a band plan could be drawn at all, and only gives it back when the feature is off, has no data, or there is no spectrum. The bookmark rail gets the same treatment for consistency, though it never moved anything — it is absolutely positioned over the top of the overview. It stays up and blank rather than vanishing. Which exposes something the rail was already doing wrong: it covers the top of the plot, and a bare div still takes pointer events, so whenever bookmarks were in range that band of the overview could not be dragged or scrolled. Only the chips are targets now. tests/spectrum-layout.mjs covers this: it streams spectrum frames, tunes between a band with bookmarks and allocations and one with neither, and asserts nothing moves and that the rail lets clicks through. Signed-off-by: Stan Grams <sjg@haxx.space>
128 lines
5.3 KiB
JavaScript
128 lines
5.3 KiB
JavaScript
// SPDX-FileCopyrightText: 2026 Stan Grams <sjg@haxx.space>
|
|
//
|
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
|
|
// Geometry of the spectrum area while tuning. Everything above the spectrum is
|
|
// driven by what happens to be in the visible range — band plan allocations,
|
|
// bookmarks — and the strips that show them used to appear and disappear with
|
|
// it, so tuning across a band edge moved the whole page under the operator's
|
|
// cursor. Nothing else in the suite serves a rig with a spectrum.
|
|
|
|
import assert from "node:assert/strict";
|
|
import { chromium } from "playwright-core";
|
|
import { startBrowser, startWebFixture } from "./web-fixture.mjs";
|
|
|
|
/* global document */
|
|
|
|
const BOOKMARKS = [
|
|
{ id: "b1", name: "40m FT8", freq_hz: 7074000, mode: "DIG", category: "Digital", comment: "", locator: "" },
|
|
{ id: "b2", name: "40m CW", freq_hz: 7030000, mode: "CW", category: "", comment: "", locator: "" },
|
|
];
|
|
const BANDPLAN = {
|
|
iaru1: {
|
|
bands: [{
|
|
name: "40m",
|
|
low_hz: 7000000,
|
|
high_hz: 7200000,
|
|
segments: [
|
|
{ low_hz: 7000000, high_hz: 7040000, mode: "CW", label: "CW" },
|
|
{ low_hz: 7040000, high_hz: 7200000, mode: "All", label: "All modes" },
|
|
],
|
|
}],
|
|
},
|
|
};
|
|
// 40m has both bookmarks and allocations; 20m has neither.
|
|
const BAND_WITH_CONTENT = 7074000;
|
|
const BAND_WITHOUT_CONTENT = 14074000;
|
|
|
|
const fixture = await startWebFixture({
|
|
spectrum: true,
|
|
bookmarks: BOOKMARKS,
|
|
bandplan: BANDPLAN,
|
|
bandplanEnabled: true,
|
|
});
|
|
const { browser, page, runtimeErrors } = await startBrowser(chromium);
|
|
|
|
function readGeometry() {
|
|
const top = (selector) => {
|
|
const el = document.querySelector(selector);
|
|
return el ? Math.round(el.getBoundingClientRect().top) : null;
|
|
};
|
|
const axis = document.getElementById("spectrum-bookmark-axis");
|
|
const strip = document.getElementById("spectrum-bandplan-strip");
|
|
return {
|
|
chips: axis.querySelectorAll(".spectrum-bookmark-chip").length,
|
|
axisEmpty: axis.classList.contains("bm-axis-empty"),
|
|
stripReserved: strip.classList.contains("bp-visible"),
|
|
stripEmpty: strip.classList.contains("bp-empty"),
|
|
overviewTop: top(".overview-strip"),
|
|
spectrumTop: top("#spectrum-panel"),
|
|
controlsTop: top(".controls-row"),
|
|
footerTop: top(".footer"),
|
|
docHeight: document.documentElement.scrollHeight,
|
|
};
|
|
}
|
|
|
|
const layoutOf = (geometry) => ({
|
|
overviewTop: geometry.overviewTop,
|
|
spectrumTop: geometry.spectrumTop,
|
|
controlsTop: geometry.controlsTop,
|
|
footerTop: geometry.footerTop,
|
|
docHeight: geometry.docHeight,
|
|
});
|
|
|
|
async function tuneTo(hz) {
|
|
fixture.setCenterHz(hz);
|
|
await page.waitForTimeout(900);
|
|
return page.evaluate(readGeometry);
|
|
}
|
|
|
|
try {
|
|
await page.setViewportSize({ width: 1600, height: 950 });
|
|
await page.goto(fixture.origin, { waitUntil: "domcontentloaded" });
|
|
await page.locator("#content").waitFor({ state: "visible" });
|
|
await page.locator("#spectrum-panel").waitFor({ state: "visible" });
|
|
await page.waitForTimeout(1500);
|
|
|
|
const populated = await tuneTo(BAND_WITH_CONTENT);
|
|
assert.equal(populated.chips, BOOKMARKS.length, `expected both bookmarks, saw ${populated.chips}`);
|
|
assert.equal(populated.axisEmpty, false, "bookmark rail claims to be empty with chips in it");
|
|
assert.equal(populated.stripReserved, true, "band plan strip is missing on a band with allocations");
|
|
assert.equal(populated.stripEmpty, false, "band plan strip claims to be empty with segments in it");
|
|
|
|
const bare = await tuneTo(BAND_WITHOUT_CONTENT);
|
|
assert.equal(bare.chips, 0, `expected no bookmarks on ${BAND_WITHOUT_CONTENT}Hz, saw ${bare.chips}`);
|
|
assert.equal(bare.axisEmpty, true, "bookmark rail should show its placeholder");
|
|
assert.equal(bare.stripReserved, true, "band plan strip gave its height back");
|
|
assert.equal(bare.stripEmpty, true, "band plan strip should be marked empty");
|
|
|
|
// The point of both placeholders: tuning must not move anything.
|
|
assert.deepEqual(layoutOf(bare), layoutOf(populated),
|
|
`tuning off the band moved the page: ${JSON.stringify(populated)} -> ${JSON.stringify(bare)}`);
|
|
|
|
const back = await tuneTo(BAND_WITH_CONTENT);
|
|
assert.equal(back.chips, BOOKMARKS.length, "bookmarks did not come back");
|
|
assert.deepEqual(layoutOf(back), layoutOf(populated), "tuning back moved the page");
|
|
|
|
// The rail covers the top of the overview, so only the chips may take
|
|
// pointer events — the rest has to fall through to the plot behind it.
|
|
const hits = await page.evaluate(() => {
|
|
const chip = document.querySelector("#spectrum-bookmark-axis .spectrum-bookmark-chip");
|
|
const chipRect = chip.getBoundingClientRect();
|
|
const axisRect = document.getElementById("spectrum-bookmark-axis").getBoundingClientRect();
|
|
const onChip = document.elementFromPoint(chipRect.left + chipRect.width / 2, chipRect.top + chipRect.height / 2);
|
|
const besideChip = document.elementFromPoint(axisRect.right - 30, axisRect.top + 10);
|
|
return {
|
|
chip: onChip?.closest(".spectrum-bookmark-chip") ? "chip" : (onChip?.id || onChip?.tagName),
|
|
besideChip: besideChip?.id || besideChip?.tagName,
|
|
};
|
|
});
|
|
assert.equal(hits.chip, "chip", `chip is not clickable, hit ${hits.chip}`);
|
|
assert.equal(hits.besideChip, "overview-canvas", `rail swallows events, hit ${hits.besideChip}`);
|
|
|
|
assert.deepEqual(runtimeErrors, []);
|
|
} finally {
|
|
await browser.close();
|
|
await fixture.close();
|
|
}
|