[fix](trx-frontend-http): route feature bundles through the host contract
CI / lint (pull_request) Failing after 2s
CI / test (pull_request) Failing after 1s
CI / frontend (pull_request) Failing after 41s
CI / reuse (pull_request) Failing after 2s
CI / lint (push) Failing after 2s
CI / test (push) Failing after 2s
CI / frontend (push) Failing after 36s
CI / reuse (push) Failing after 1s
CI / lint (pull_request) Failing after 2s
CI / test (pull_request) Failing after 1s
CI / frontend (pull_request) Failing after 41s
CI / reuse (pull_request) Failing after 2s
CI / lint (push) Failing after 2s
CI / test (push) Failing after 2s
CI / frontend (push) Failing after 36s
CI / reuse (push) Failing after 1s
The bookmark fix addressed one instance of a defect the TypeScript migration left across the feature entries. app.js stopped being a classic script, so its top-level declarations are no longer shared globals, but the converted entries kept reading them as window properties that nothing publishes. Restore the broken behavior: - ais, aprs, hf-aprs read serverLat, serverLon and haversineKm as undefined, so every positioned packet rendered an empty distance. - ais, aprs, hf-aprs, cw, sat, vdes, wefax, wspr called an undefined postPath, so clear-history and decoder toggles threw. - scheduler read authRole as undefined, so the lazy-load path never self-initialized and the Settings tab opened an inert scheduler. - background-decode read authEnabled as undefined, so control gating fell back to role-only. - vchan read fifteen application values and services as undefined: mode and bandwidth sync, the out-of-band hint, RX audio restart, and the frequency field all silently no-opped on a virtual channel. - vchan wrapped window.refreshFreqDisplay, capturing an undefined original exactly as it did for setRigFrequency, so leaving a channel never restored the application's own frequency display. - _audioChannelOverride was a const that nothing could assign, so RX audio always subscribed to the primary channel. - ftx-family read fmtTime, a helper legacy ft8.js owned locally, so decode bar timestamps rendered empty. Declare the contract once in plugins/host.ts and import it from the feature entries, rather than restoring globals that docs/frontend-architecture.md excludes. trx.state gains jogUnit, rxActive and audioChannelOverride, and makes lastModeName writable; trx.core gains the tuning, RDS, WFM, jog and RX audio services the entries need. vchan interception moves to an interceptFreqDisplay service method that refreshFreqDisplay calls, matching the frequency, mode and bandwidth interception it already registers. Reading registry-built elements through a strict lookup is the same defect as in bookmarks: renderTimelineNeedle guards its result, but schedulerEl throws, so the now-initializing scheduler crashed on the timeline needle group that its own SVG creates. Feature tests move onto a shared host fixture, and entries that now import a common module are bundled through bundleEntry like the other shared-module entries. Covers scheduler self-initialization and the distance path that the bare window reads broke. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GdyUjuXejCEfiub675z6cz Signed-off-by: Stan Grams <sjg@haxx.space>
This commit was merged in pull request #23.
This commit is contained in:
@@ -3,14 +3,15 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
import assert from "node:assert/strict";
|
||||
import { readFile } from "node:fs/promises";
|
||||
import test from "node:test";
|
||||
import vm from "node:vm";
|
||||
import { bundleEntry } from "./bundle-entry.mjs";
|
||||
import { createHost } from "./host-fixture.mjs";
|
||||
|
||||
test("AIS entry forwards positioned vessels with normalized metadata", async () => {
|
||||
const forwarded = [];
|
||||
const window = {
|
||||
...createHost(),
|
||||
trxUi: { confirm: async () => true },
|
||||
aisMapAddVessel: (message) => { forwarded.push(message); },
|
||||
};
|
||||
@@ -25,7 +26,7 @@ test("AIS entry forwards positioned vessels with normalized metadata", async ()
|
||||
console,
|
||||
});
|
||||
const runtime = await bundleEntry(new URL("../src/plugin-runtime.ts", import.meta.url));
|
||||
const source = await readFile(new URL("../../assets/web/generated/ais.js", import.meta.url), "utf8");
|
||||
const source = await bundleEntry(new URL("../src/plugins/ais.ts", import.meta.url));
|
||||
new vm.Script(runtime).runInContext(context);
|
||||
new vm.Script(source).runInContext(context);
|
||||
|
||||
|
||||
@@ -6,10 +6,12 @@ import assert from "node:assert/strict";
|
||||
import test from "node:test";
|
||||
import vm from "node:vm";
|
||||
import { bundleEntry } from "./bundle-entry.mjs";
|
||||
import { createHost } from "./host-fixture.mjs";
|
||||
|
||||
test("APRS entry normalizes positioned packets without remote symbol assets", async () => {
|
||||
const forwarded = [];
|
||||
const window = {
|
||||
...createHost(),
|
||||
trxUi: { confirm: async () => true },
|
||||
aprsMapAddStation: (...args) => { forwarded.push(args); },
|
||||
};
|
||||
@@ -38,3 +40,49 @@ test("APRS entry normalizes positioned packets without remote symbol assets", as
|
||||
assert.equal(forwarded[0][1], 54.5);
|
||||
assert.equal(forwarded[0][6].rig_id, null);
|
||||
});
|
||||
|
||||
// Distance is computed from the receiver position and the great-circle helper,
|
||||
// both of which the entry reads through the host contract. These used to be
|
||||
// bare window properties that the module graph stopped publishing, so the
|
||||
// distance column rendered empty for every positioned packet.
|
||||
test("APRS distance text uses the receiver position from the host contract", async () => {
|
||||
const distanceArgs = [];
|
||||
const host = createHost({
|
||||
state: { serverLat: 54.0, serverLon: 18.0 },
|
||||
core: {
|
||||
haversineKm: (lat1, lon1, lat2, lon2) => {
|
||||
distanceArgs.push([lat1, lon1, lat2, lon2]);
|
||||
return 61.7;
|
||||
},
|
||||
},
|
||||
});
|
||||
const window = { ...host, trxUi: { confirm: async () => true }, aprsMapAddStation: () => {} };
|
||||
const node = () => ({
|
||||
innerHTML: "", textContent: "", className: "", style: {}, dataset: {},
|
||||
appendChild(child) { return child; }, addEventListener() {}, replaceChildren() {},
|
||||
querySelector: () => null, querySelectorAll: () => [], setAttribute() {},
|
||||
classList: { add() {}, toggle() {} },
|
||||
});
|
||||
const context = vm.createContext({
|
||||
window,
|
||||
document: {
|
||||
getElementById: (id) => (id === "aprs-packets" ? node() : null),
|
||||
querySelectorAll: () => [],
|
||||
createElement: node,
|
||||
createDocumentFragment: node,
|
||||
},
|
||||
navigator: {},
|
||||
Date, Number, String, Array, Set, Reflect, console,
|
||||
});
|
||||
const runtime = await bundleEntry(new URL("../src/plugin-runtime.ts", import.meta.url));
|
||||
const source = await bundleEntry(new URL("../src/plugins/aprs.ts", import.meta.url));
|
||||
new vm.Script(runtime).runInContext(context);
|
||||
new vm.Script(source).runInContext(context);
|
||||
|
||||
window.trxPluginRuntime.dispatch("aprs", {
|
||||
src_call: "SP0ABC", dest_call: "APRS", crc_ok: true, lat: 54.5, lon: 18.5,
|
||||
symbol_table: "/", symbol_code: ">",
|
||||
});
|
||||
|
||||
assert.deepEqual(distanceArgs, [[54.0, 18.0, 54.5, 18.5]]);
|
||||
});
|
||||
|
||||
+4
-2
@@ -3,13 +3,15 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
import assert from "node:assert/strict";
|
||||
import { readFile } from "node:fs/promises";
|
||||
import test from "node:test";
|
||||
import vm from "node:vm";
|
||||
import { createHost } from "./host-fixture.mjs";
|
||||
import { bundleEntry } from "./bundle-entry.mjs";
|
||||
|
||||
test("background decode loads configuration for the explicitly selected rig", async () => {
|
||||
const requested = [];
|
||||
const window = {
|
||||
...createHost(),
|
||||
decoderRegistry: [],
|
||||
trxUi: { confirm: async () => true },
|
||||
};
|
||||
@@ -35,7 +37,7 @@ test("background decode loads configuration for the explicitly selected rig", as
|
||||
Error,
|
||||
console,
|
||||
});
|
||||
const source = await readFile(new URL("../../assets/web/generated/background-decode.js", import.meta.url), "utf8");
|
||||
const source = await bundleEntry(new URL("../src/plugins/background-decode.ts", import.meta.url));
|
||||
new vm.Script(source).runInContext(context);
|
||||
|
||||
window.trx.modules.backgroundDecode.initialize("rig/a", "control");
|
||||
|
||||
@@ -3,9 +3,9 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
import assert from "node:assert/strict";
|
||||
import { readFile } from "node:fs/promises";
|
||||
import test from "node:test";
|
||||
import vm from "node:vm";
|
||||
import { bundleEntry } from "./bundle-entry.mjs";
|
||||
|
||||
class ElementFixture {
|
||||
constructor() {
|
||||
@@ -66,7 +66,7 @@ function documentFixture(element) {
|
||||
};
|
||||
}
|
||||
|
||||
const source = await readFile(new URL("../../assets/web/generated/bookmarks.js", import.meta.url), "utf8");
|
||||
const source = await bundleEntry(new URL("../src/plugins/bookmarks.ts", import.meta.url));
|
||||
|
||||
test("bookmarks register an explicit typed service for application consumers", async () => {
|
||||
const elements = new Map();
|
||||
|
||||
@@ -3,10 +3,10 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
import assert from "node:assert/strict";
|
||||
import { readFile } from "node:fs/promises";
|
||||
import test from "node:test";
|
||||
import vm from "node:vm";
|
||||
import { bundleEntry } from "./bundle-entry.mjs";
|
||||
import { createHost } from "./host-fixture.mjs";
|
||||
|
||||
class ElementFixture {
|
||||
constructor() { this.children = []; this.textContent = ""; this.style = {}; this.classList = { toggle() {} }; }
|
||||
@@ -21,7 +21,7 @@ test("CW entry appends server-decoded text and registers lifecycle callbacks", a
|
||||
const output = new ElementFixture();
|
||||
const status = new ElementFixture();
|
||||
const elements = new Map([["cw-output", output], ["cw-status", status]]);
|
||||
const window = { trxUi: { confirm: async () => true }, addEventListener() {} };
|
||||
const window = { ...createHost(), trxUi: { confirm: async () => true }, addEventListener() {} };
|
||||
const context = vm.createContext({
|
||||
window,
|
||||
document: {
|
||||
@@ -39,7 +39,7 @@ test("CW entry appends server-decoded text and registers lifecycle callbacks", a
|
||||
console,
|
||||
});
|
||||
const runtime = await bundleEntry(new URL("../src/plugin-runtime.ts", import.meta.url));
|
||||
const source = await readFile(new URL("../../assets/web/generated/cw.js", import.meta.url), "utf8");
|
||||
const source = await bundleEntry(new URL("../src/plugins/cw.ts", import.meta.url));
|
||||
new vm.Script(runtime).runInContext(context);
|
||||
new vm.Script(source).runInContext(context);
|
||||
|
||||
|
||||
@@ -6,11 +6,13 @@ import assert from "node:assert/strict";
|
||||
import test from "node:test";
|
||||
import vm from "node:vm";
|
||||
import { bundleEntry } from "./bundle-entry.mjs";
|
||||
import { createHost } from "./host-fixture.mjs";
|
||||
|
||||
test("FT2 entry normalizes audio offsets and registers typed callbacks", async () => {
|
||||
let barRenderer;
|
||||
const mapMessages = [];
|
||||
const window = {
|
||||
...createHost(),
|
||||
ft8BaseHz: 14_074_000,
|
||||
trxUi: { confirm: async () => true },
|
||||
ft8ExtractAllGrids: () => ["JO91"],
|
||||
@@ -46,6 +48,7 @@ test("FT2 entry normalizes audio offsets and registers typed callbacks", async (
|
||||
test("FT8 entry installs shared parsing without relying on script globals", async () => {
|
||||
const forwarded = [];
|
||||
const window = {
|
||||
...createHost(),
|
||||
ft8BaseHz: 7_074_000,
|
||||
trxUi: { confirm: async () => true },
|
||||
mapAddLocator: (...args) => { forwarded.push(args); },
|
||||
|
||||
@@ -6,9 +6,10 @@ import assert from "node:assert/strict";
|
||||
import test from "node:test";
|
||||
import vm from "node:vm";
|
||||
import { bundleEntry } from "./bundle-entry.mjs";
|
||||
import { createHost } from "./host-fixture.mjs";
|
||||
|
||||
test("HF APRS entry uses shared typed normalization and local symbols", async () => {
|
||||
const window = { trxUi: { confirm: async () => true } };
|
||||
const window = { ...createHost(), trxUi: { confirm: async () => true } };
|
||||
const context = vm.createContext({
|
||||
window,
|
||||
document: { getElementById: () => null, querySelectorAll: () => [] },
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
// SPDX-FileCopyrightText: 2026 Stan Grams <sjg@haxx.space>
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
// Mirrors the `window.trx` host contract that app.ts publishes and that
|
||||
// src/plugins/host.ts consumes. Feature bundles read all application state
|
||||
// and services through it, so every plugin test needs it on its stub window.
|
||||
|
||||
export function createHost({ state = {}, core = {}, modules = {} } = {}) {
|
||||
const calls = [];
|
||||
const record = (name, result) => (...args) => {
|
||||
calls.push({ name, args });
|
||||
return typeof result === "function" ? result(...args) : result;
|
||||
};
|
||||
return {
|
||||
calls,
|
||||
trx: {
|
||||
state: {
|
||||
serverLat: null,
|
||||
serverLon: null,
|
||||
authEnabled: false,
|
||||
authRole: "control",
|
||||
lastActiveRigId: null,
|
||||
lastRigIds: [],
|
||||
lastRigDisplayNames: {},
|
||||
lastFreqHz: null,
|
||||
lastSpectrumData: null,
|
||||
jogUnit: 1000,
|
||||
rxActive: false,
|
||||
decoderRegistry: [],
|
||||
lastModeName: "",
|
||||
currentBandwidthHz: 2400,
|
||||
audioChannelOverride: null,
|
||||
...state,
|
||||
},
|
||||
core: {
|
||||
postPath: record("postPath", async () => undefined),
|
||||
applyLocalTunedFrequency: record("applyLocalTunedFrequency"),
|
||||
armOptimisticFrequency: record("armOptimisticFrequency"),
|
||||
escapeMapHtml: (value) => String(value)
|
||||
.replaceAll("&", "&").replaceAll("<", "<")
|
||||
.replaceAll(">", ">").replaceAll('"', """),
|
||||
haversineKm: record("haversineKm", () => 0),
|
||||
showHint: record("showHint"),
|
||||
formatFreqForStep: (hz) => String(hz),
|
||||
refreshFreqDisplay: record("refreshFreqDisplay"),
|
||||
setJogDivisor: record("setJogDivisor"),
|
||||
mwDefaultsForMode: record("mwDefaultsForMode", () => [0, 0, 0, 0]),
|
||||
resetRdsDisplay: record("resetRdsDisplay"),
|
||||
positionRdsPsOverlay: record("positionRdsPsOverlay"),
|
||||
updateWfmControls: record("updateWfmControls"),
|
||||
updateSdrSquelchControlVisibility: record("updateSdrSquelchControlVisibility"),
|
||||
updateDocumentTitle: record("updateDocumentTitle"),
|
||||
activeChannelRds: record("activeChannelRds", () => null),
|
||||
startRxAudio: record("startRxAudio"),
|
||||
stopRxAudio: record("stopRxAudio"),
|
||||
setRigFrequency: record("setRigFrequency"),
|
||||
syncBandwidthInput: record("syncBandwidthInput"),
|
||||
scheduleSpectrumDraw: record("scheduleSpectrumDraw"),
|
||||
onDecoderRegistryReady: record("onDecoderRegistryReady"),
|
||||
...core,
|
||||
},
|
||||
modules,
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -3,14 +3,15 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
import assert from "node:assert/strict";
|
||||
import { readFile } from "node:fs/promises";
|
||||
import test from "node:test";
|
||||
import vm from "node:vm";
|
||||
import { bundleEntry } from "./bundle-entry.mjs";
|
||||
import { createHost } from "./host-fixture.mjs";
|
||||
|
||||
test("satellite entry registers lifecycle callbacks and forwards georeferenced images", async () => {
|
||||
const overlays = [];
|
||||
const window = {
|
||||
...createHost(),
|
||||
trxUi: { confirm: async () => true },
|
||||
addSatMapOverlay: (image) => { overlays.push(image); },
|
||||
};
|
||||
@@ -28,7 +29,7 @@ test("satellite entry registers lifecycle callbacks and forwards georeferenced i
|
||||
console,
|
||||
});
|
||||
const runtime = await bundleEntry(new URL("../src/plugin-runtime.ts", import.meta.url));
|
||||
const source = await readFile(new URL("../../assets/web/generated/sat.js", import.meta.url), "utf8");
|
||||
const source = await bundleEntry(new URL("../src/plugins/sat.ts", import.meta.url));
|
||||
new vm.Script(runtime).runInContext(context);
|
||||
new vm.Script(source).runInContext(context);
|
||||
|
||||
|
||||
@@ -3,12 +3,15 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
import assert from "node:assert/strict";
|
||||
import { readFile } from "node:fs/promises";
|
||||
import test from "node:test";
|
||||
import vm from "node:vm";
|
||||
import { createHost } from "./host-fixture.mjs";
|
||||
import { bundleEntry } from "./bundle-entry.mjs";
|
||||
|
||||
test("scheduler registers a typed module service without lifecycle globals", async () => {
|
||||
const window = { trx: { modules: {} }, trxUi: { confirm: async () => true } };
|
||||
// No role known yet: the entry registers its service and waits for the
|
||||
// application to drive initialization.
|
||||
const window = { ...createHost({ state: { authRole: null } }), trxUi: { confirm: async () => true } };
|
||||
const context = vm.createContext({
|
||||
window,
|
||||
document: {
|
||||
@@ -31,7 +34,7 @@ test("scheduler registers a typed module service without lifecycle globals", asy
|
||||
WeakSet,
|
||||
console,
|
||||
});
|
||||
const source = await readFile(new URL("../../assets/web/generated/scheduler.js", import.meta.url), "utf8");
|
||||
const source = await bundleEntry(new URL("../src/plugins/scheduler.ts", import.meta.url));
|
||||
new vm.Script(source).runInContext(context);
|
||||
|
||||
const service = window.trx.modules.scheduler;
|
||||
@@ -41,3 +44,70 @@ test("scheduler registers a typed module service without lifecycle globals", asy
|
||||
assert.equal(window.initScheduler, undefined);
|
||||
assert.equal(window.schedulerBridge, undefined);
|
||||
});
|
||||
|
||||
// Lazy-load case: the settings tab is opened after boot, so the application has
|
||||
// already passed initSettingsUI() and the entry must initialize itself from the
|
||||
// host state. This read used to come from a bare window property that the
|
||||
// module graph stopped publishing, so the scheduler never started.
|
||||
test("scheduler self-initializes for the active rig when a role is already known", async () => {
|
||||
class ElementFixture {
|
||||
constructor() {
|
||||
this.value = "";
|
||||
this.textContent = "";
|
||||
this.innerHTML = "";
|
||||
this.style = {};
|
||||
this.dataset = {};
|
||||
this.options = [];
|
||||
this.classList = { add() {}, remove() {}, toggle() {}, contains: () => false };
|
||||
this.children = [];
|
||||
}
|
||||
addEventListener() {}
|
||||
appendChild(child) { this.children.push(child); return child; }
|
||||
remove() {}
|
||||
focus() {}
|
||||
closest() { return null; }
|
||||
querySelector() { return null; }
|
||||
querySelectorAll() { return []; }
|
||||
}
|
||||
const elements = new Map();
|
||||
const fetched = [];
|
||||
const element = (id) => {
|
||||
if (!elements.has(id)) elements.set(id, new ElementFixture());
|
||||
return elements.get(id);
|
||||
};
|
||||
const window = {
|
||||
...createHost({ state: { authRole: "control", lastActiveRigId: "sdr" } }),
|
||||
trxUi: { confirm: async () => true },
|
||||
};
|
||||
const context = vm.createContext({
|
||||
window,
|
||||
document: {
|
||||
activeElement: null,
|
||||
addEventListener() {},
|
||||
getElementById: element,
|
||||
querySelector: () => new ElementFixture(),
|
||||
querySelectorAll: () => [],
|
||||
createElement: () => new ElementFixture(),
|
||||
},
|
||||
localStorage: { getItem: () => null, setItem() {} },
|
||||
fetch: async (url) => { fetched.push(String(url)); return { ok: true, json: async () => ({}) }; },
|
||||
setInterval: () => 1,
|
||||
clearInterval() {},
|
||||
setTimeout: () => 1,
|
||||
HTMLElement: ElementFixture,
|
||||
Element: ElementFixture,
|
||||
Date,
|
||||
Number,
|
||||
String,
|
||||
Array,
|
||||
Promise,
|
||||
WeakSet,
|
||||
console,
|
||||
});
|
||||
const source = await bundleEntry(new URL("../src/plugins/scheduler.ts", import.meta.url));
|
||||
new vm.Script(source).runInContext(context);
|
||||
await new Promise((resolve) => setImmediate(resolve));
|
||||
|
||||
assert.ok(fetched.some((url) => url.includes("scheduler") && url.includes("sdr")),
|
||||
`expected a scheduler load for the active rig, saw ${JSON.stringify(fetched)}`);
|
||||
});
|
||||
|
||||
@@ -3,12 +3,13 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
import assert from "node:assert/strict";
|
||||
import { readFile } from "node:fs/promises";
|
||||
import test from "node:test";
|
||||
import vm from "node:vm";
|
||||
import { createHost } from "./host-fixture.mjs";
|
||||
import { bundleEntry } from "./bundle-entry.mjs";
|
||||
|
||||
test("virtual channels expose typed SSE and interception boundaries", async () => {
|
||||
const window = {};
|
||||
const window = { ...createHost() };
|
||||
const context = vm.createContext({
|
||||
window,
|
||||
document: { getElementById: () => null },
|
||||
@@ -25,7 +26,7 @@ test("virtual channels expose typed SSE and interception boundaries", async () =
|
||||
Error,
|
||||
console,
|
||||
});
|
||||
const source = await readFile(new URL("../../assets/web/generated/vchan.js", import.meta.url), "utf8");
|
||||
const source = await bundleEntry(new URL("../src/plugins/vchan.ts", import.meta.url));
|
||||
new vm.Script(source).runInContext(context);
|
||||
|
||||
const service = window.trx.modules.vchan;
|
||||
|
||||
@@ -3,14 +3,15 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
import assert from "node:assert/strict";
|
||||
import { readFile } from "node:fs/promises";
|
||||
import test from "node:test";
|
||||
import vm from "node:vm";
|
||||
import { bundleEntry } from "./bundle-entry.mjs";
|
||||
import { createHost } from "./host-fixture.mjs";
|
||||
|
||||
test("VDES entry normalizes and forwards positioned server messages", async () => {
|
||||
const forwarded = [];
|
||||
const window = {
|
||||
...createHost(),
|
||||
trxUi: { confirm: async () => true },
|
||||
vdesMapAddPoint: (message) => { forwarded.push(message); },
|
||||
};
|
||||
@@ -24,7 +25,7 @@ test("VDES entry normalizes and forwards positioned server messages", async () =
|
||||
console,
|
||||
});
|
||||
const runtime = await bundleEntry(new URL("../src/plugin-runtime.ts", import.meta.url));
|
||||
const source = await readFile(new URL("../../assets/web/generated/vdes.js", import.meta.url), "utf8");
|
||||
const source = await bundleEntry(new URL("../src/plugins/vdes.ts", import.meta.url));
|
||||
new vm.Script(runtime).runInContext(context);
|
||||
new vm.Script(source).runInContext(context);
|
||||
|
||||
|
||||
@@ -3,14 +3,14 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
import assert from "node:assert/strict";
|
||||
import { readFile } from "node:fs/promises";
|
||||
import test from "node:test";
|
||||
import vm from "node:vm";
|
||||
import { bundleEntry } from "./bundle-entry.mjs";
|
||||
import { createHost } from "./host-fixture.mjs";
|
||||
|
||||
test("WEFAX entry exposes typed lifecycle handlers and renders decoder state", async () => {
|
||||
const status = { textContent: "", style: { color: "" } };
|
||||
const window = {};
|
||||
const window = { ...createHost() };
|
||||
const context = vm.createContext({
|
||||
window,
|
||||
document: { getElementById: (id) => id === "wefax-status" ? status : null },
|
||||
@@ -21,7 +21,7 @@ test("WEFAX entry exposes typed lifecycle handlers and renders decoder state", a
|
||||
console,
|
||||
});
|
||||
const runtime = await bundleEntry(new URL("../src/plugin-runtime.ts", import.meta.url));
|
||||
const source = await readFile(new URL("../../assets/web/generated/wefax.js", import.meta.url), "utf8");
|
||||
const source = await bundleEntry(new URL("../src/plugins/wefax.ts", import.meta.url));
|
||||
new vm.Script(runtime).runInContext(context);
|
||||
new vm.Script(source).runInContext(context);
|
||||
|
||||
|
||||
@@ -3,14 +3,15 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
import assert from "node:assert/strict";
|
||||
import { readFile } from "node:fs/promises";
|
||||
import test from "node:test";
|
||||
import vm from "node:vm";
|
||||
import { bundleEntry } from "./bundle-entry.mjs";
|
||||
import { createHost } from "./host-fixture.mjs";
|
||||
|
||||
test("WSPR entry forwards decoded locators at their absolute frequency", async () => {
|
||||
const forwarded = [];
|
||||
const window = {
|
||||
...createHost(),
|
||||
ft8BaseHz: 14_095_600,
|
||||
trxUi: { confirm: async () => true },
|
||||
mapAddLocator: (...args) => { forwarded.push(args); },
|
||||
@@ -27,7 +28,7 @@ test("WSPR entry forwards decoded locators at their absolute frequency", async (
|
||||
console,
|
||||
});
|
||||
const runtime = await bundleEntry(new URL("../src/plugin-runtime.ts", import.meta.url));
|
||||
const source = await readFile(new URL("../../assets/web/generated/wspr.js", import.meta.url), "utf8");
|
||||
const source = await bundleEntry(new URL("../src/plugins/wspr.ts", import.meta.url));
|
||||
new vm.Script(runtime).runInContext(context);
|
||||
new vm.Script(source).runInContext(context);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user