"use strict"; const test = require("node:test"); const assert = require("node:assert/strict"); const {loadApp, SHELL_HTML} = require("./harness"); const {makeServer} = require("./fixture"); function menuKeys(document) { return Array.from(document.querySelectorAll("#main-menu li")).map(li => li.getAttribute("data-menu")); } test("createLayout keeps the menu nodes when the visible items are unchanged", () => { const app = loadApp({server: makeServer()}); app.window.createLayout(); const before = Array.from(app.document.querySelectorAll("#main-menu li")); const focused = before[2]; focused.setAttribute("tabindex", "0"); focused.focus(); assert.equal(app.document.activeElement, focused); app.window.SERVER = makeServer({clientInfo: {streams: "9", errors: "3"}}); app.window.createLayout(); const after = Array.from(app.document.querySelectorAll("#main-menu li")); assert.equal(after.length, before.length); for (let i = 0; i < before.length; i++) { assert.equal(after[i], before[i], "menu node " + i + " is the same object"); } assert.equal(app.document.activeElement, focused, "focus survives the refresh"); assert.equal(app.document.getElementById("streams").textContent, "9", "client info refreshed"); const errorCard = Array.from(app.document.querySelectorAll("#status-cards .status-card")).find(c => c.textContent.startsWith("Errors")); assert.ok(errorCard.className.includes("status-card-error"), "status cards refreshed"); }); test("createLayout rebuilds the menu when the visible items change", () => { const app = loadApp({server: makeServer()}); app.window.createLayout(); assert.deepEqual(menuKeys(app.document), ["playlist", "filter", "xmltv", "mapping", "settings", "log"]); const playlist = app.document.querySelector('#main-menu li[data-menu="playlist"]'); app.window.SERVER = makeServer({settings: {"authentication.web": true}}); app.window.createLayout(); assert.deepEqual(menuKeys(app.document), ["playlist", "filter", "xmltv", "mapping", "users", "settings", "log", "logout"]); assert.notEqual(app.document.querySelector('#main-menu li[data-menu="playlist"]'), playlist, "list was rebuilt"); const active = app.document.querySelector("#main-menu li.menu-active"); assert.ok(active, "active item restored after rebuild"); assert.equal(active.getAttribute("data-menu"), "playlist"); app.window.SERVER = makeServer({clientInfo: {epgSource: "PMS"}}); app.window.createLayout(); assert.deepEqual(menuKeys(app.document), ["playlist", "filter", "settings", "log"]); }); test("createLayout keeps the active menu when another item is selected", () => { const app = loadApp({server: makeServer()}); app.window.createLayout(); app.document.querySelector('#main-menu li[data-menu="mapping"]').click(); assert.equal(app.document.body.getAttribute("data-active-menu"), "mapping"); app.window.SERVER = makeServer(); app.window.createLayout(); assert.equal(app.document.body.getAttribute("data-active-menu"), "mapping"); assert.equal(app.document.querySelector("#main-menu li.menu-active").getAttribute("data-menu"), "mapping"); assert.equal(app.document.querySelectorAll("#content_table tr").length, 6, "mapping content left in place"); }); test("alertUser mirrors the message into the existing live region", () => { const app = loadApp({server: makeServer()}); app.window.alertUser("Something went wrong"); assert.deepEqual(app.alerts, ["Something went wrong"]); assert.equal(app.document.getElementById("sr-announcer").textContent, "Something went wrong"); assert.equal(app.document.getElementById("announcer"), null, "no second region created"); }); test("announce creates a polite live region when the page has none", () => { const html = SHELL_HTML.replace(/
]*><\/p>/, ""); const app = loadApp({server: makeServer(), html}); assert.equal(app.document.getElementById("sr-announcer"), null); app.window.announce("Saved"); const region = app.document.getElementById("announcer"); assert.ok(region, "region created"); assert.equal(region.getAttribute("aria-live"), "polite"); assert.equal(region.className, "sr-only"); assert.equal(region.textContent, "Saved"); app.window.announce("Again"); assert.equal(app.document.querySelectorAll("#announcer").length, 1); assert.equal(region.textContent, "Again"); }); test("server error responses alert and announce", () => { const app = loadApp({server: makeServer()}); app.window.createLayout(); app.evalIn('new Server("saveSettings").request({settings: {}})'); const socket = app.sockets[app.sockets.length - 1]; socket.readyState = 1; socket.onopen(); const sent = JSON.parse(socket.sent[0]); socket.onmessage({data: JSON.stringify({id: sent.id, status: false, err: "Setting rejected"})}); return new Promise(resolve => setImmediate(resolve)).then(() => { assert.deepEqual(app.alerts, ["Setting rejected"]); assert.equal(app.document.getElementById("sr-announcer").textContent, "Setting rejected"); assert.equal(app.document.getElementById("connection-indicator").className, "status-offline"); }); });