CI / lint (pull_request) Successful in 2m16s
CI / test (pull_request) Successful in 8m12s
CI / frontend (pull_request) Successful in 3m2s
CI / reuse (pull_request) Successful in 3s
CI / lint (push) Successful in 2m24s
CI / test (push) Successful in 7m28s
CI / frontend (push) Successful in 2m11s
CI / reuse (push) Successful in 3s
Resolve a table/code pair to a sprite cell in aprs-shared, and use it from both the packet lists and the map markers, which had each been printing the raw symbol character in a bordered box. A table identifier of / or \ selects the primary or alternate sheet directly. Anything else is an overlay character, which the APRS spec draws on top of the alternate symbol -- so those stack the overlay sheet over the alternate one rather than picking a sheet. Codes outside 0x21..0x7E have no cell and keep the old character box. The sheet URLs stay in the stylesheet so a min-resolution query can swap in the retina sheets; only the cell offset is computed and set inline. Map markers share the helper through the plugin chunk, so the map stays free of any remote symbol fetch. Verified in a browser against the real stylesheet and sheets: /> is a car, /_ a WX circle, /& an igate diamond, \n a red triangle, and the overlays S> and 7# carry their character on the alternate symbol. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018huL1ELyr86yVqfAabtioA Signed-off-by: Stan Grams <sjg@haxx.space>
86 lines
4.0 KiB
JavaScript
86 lines
4.0 KiB
JavaScript
// SPDX-FileCopyrightText: 2026 Stan Grams <sjg@haxx.space>
|
|
//
|
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
|
|
import assert from "node:assert/strict";
|
|
import test from "node:test";
|
|
import vm from "node:vm";
|
|
import { readFile } from "node:fs/promises";
|
|
import { bundleEntry } from "./bundle-entry.mjs";
|
|
|
|
const sharedUrl = new URL("../src/plugins/aprs-shared.ts", import.meta.url);
|
|
const stylePath = new URL("../../assets/web/style.css", import.meta.url);
|
|
|
|
async function loadShared() {
|
|
const source = await bundleEntry(sharedUrl, "trxAprsShared");
|
|
const context = vm.createContext({ Math, String, Number, Array, Date, Set, console });
|
|
new vm.Script(source).runInContext(context);
|
|
return context.trxAprsShared;
|
|
}
|
|
|
|
// The sheets are 16x6 grids of 24px cells covering 0x21..0x7E, so cell index
|
|
// is `code - 0x21`. Getting this wrong shifts every station to a neighbouring
|
|
// icon, which is invisible in a screenshot but wrong on every packet.
|
|
test("sprite cells are indexed from the first printable symbol code", async () => {
|
|
const { aprsSymbolSprite } = await loadShared();
|
|
|
|
const first = aprsSymbolSprite("/", "!");
|
|
assert.equal(first.className, "aprs-symbol-primary");
|
|
assert.equal(first.backgroundPosition, "0px 0px");
|
|
assert.equal(first.label, "Primary APRS symbol /!");
|
|
// '>' is 0x3E -> index 29 -> column 13, row 1.
|
|
assert.equal(aprsSymbolSprite("/", ">").backgroundPosition, "-312px -24px");
|
|
// '~' is 0x7E -> index 93 -> the last cell of the last row.
|
|
assert.equal(aprsSymbolSprite("/", "~").backgroundPosition, "-312px -120px");
|
|
});
|
|
|
|
test("the table identifier selects the primary, alternate, or overlay sheet", async () => {
|
|
const { aprsSymbolSprite } = await loadShared();
|
|
|
|
assert.equal(aprsSymbolSprite("/", "_").className, "aprs-symbol-primary");
|
|
assert.equal(aprsSymbolSprite("\\", "_").className, "aprs-symbol-alternate");
|
|
|
|
// An alphanumeric table identifier is an overlay character drawn on top of
|
|
// the alternate symbol: overlay cell first, then the symbol cell.
|
|
const overlaid = aprsSymbolSprite("S", ">");
|
|
assert.equal(overlaid.className, "aprs-symbol-overlaid");
|
|
assert.equal(overlaid.backgroundPosition, "-48px -72px, -312px -24px");
|
|
assert.equal(overlaid.label, "Alternate APRS symbol \\> with overlay S");
|
|
});
|
|
|
|
test("codes outside the sprite sheets fall back to the raw character", async () => {
|
|
const { aprsSymbolSprite, renderLocalAprsSymbol } = await loadShared();
|
|
const escape = (value) => value;
|
|
|
|
assert.equal(aprsSymbolSprite("/", " "), null);
|
|
assert.equal(aprsSymbolSprite("/", ""), null);
|
|
assert.equal(aprsSymbolSprite("/", "ab"), null);
|
|
assert.equal(aprsSymbolSprite(null, ">"), null);
|
|
|
|
assert.equal(renderLocalAprsSymbol({ symbolTable: "/", symbolCode: " " }, escape),
|
|
'<span class="aprs-symbol aprs-symbol-local" title="Primary APRS symbol "> </span>');
|
|
assert.equal(renderLocalAprsSymbol({}, escape), "");
|
|
});
|
|
|
|
test("rendered symbols carry a sprite class and an inline cell offset", async () => {
|
|
const { renderLocalAprsSymbol } = await loadShared();
|
|
const html = renderLocalAprsSymbol({ symbolTable: "/", symbolCode: ">" }, (value) => value);
|
|
|
|
assert.match(html, /class="aprs-symbol aprs-symbol-primary"/);
|
|
assert.match(html, /style="background-position:-312px -24px"/);
|
|
assert.match(html, /aria-label="Primary APRS symbol \/>"/);
|
|
assert.equal(html.includes("http"), false);
|
|
});
|
|
|
|
// The cell offsets are computed in the bundles, so the sheet URLs and the grid
|
|
// geometry have to stay in lockstep with them here.
|
|
test("the stylesheet serves every sheet locally at the sprite geometry", async () => {
|
|
const css = await readFile(stylePath, "utf8");
|
|
|
|
assert.match(css, /\.aprs-symbol\s*\{[^}]*background-size:\s*384px 144px/);
|
|
for (const sheet of ["24-0", "24-1", "24-2", "24-0-2x", "24-1-2x", "24-2-2x"]) {
|
|
assert.ok(css.includes(`url('/vendor/aprs-symbols-${sheet}.png')`), `missing sheet ${sheet}`);
|
|
}
|
|
assert.match(css, /\.aprs-symbol-overlaid\s*\{[^}]*aprs-symbols-24-2\.png'\), url\('\/vendor\/aprs-symbols-24-1\.png'\)/);
|
|
});
|