// SPDX-FileCopyrightText: 2026 Stan Grams // // 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), ' '); 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'\)/); });