// 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 { createHost } from "./host-fixture.mjs"; import { bundleEntry } from "./bundle-entry.mjs"; test("scheduler registers a typed module service without lifecycle globals", async () => { // No role known yet: the entry registers its service and waits for the // application to drive initialization. const window = { ...createHost({ state: { authEnabled: true, authRoles: [] } }), trxUi: { confirm: async () => true } }; const context = vm.createContext({ window, document: { activeElement: null, addEventListener() {}, getElementById: () => null, querySelector: () => null, }, localStorage: { getItem: () => null, setItem() {} }, setInterval: () => 1, clearInterval() {}, setTimeout: () => 1, HTMLElement: class HTMLElement {}, Element: class Element {}, 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); const service = window.trx.modules.scheduler; assert.equal(typeof service.initialize, "function"); assert.equal(typeof service.setRig, "function"); assert.equal(typeof service.getConfig, "function"); 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: { authRoles: ["administrator"], 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)}`); });