refactor: eager settings load on startup and update all frontend tests

Part A: DOMContentLoaded now async with await loadServerSettings() before
first render. Removes lazy loadServerSettings() from inside .then() block.

Part B: Fix 18 failing tests in test_app.mjs:
- Sidebar tests: replace _localStorageStore['muxplex.sidebarOpen'] with
  app._setServerSettings({sidebarOpen: ...}); update toggleSidebar mocks
  to include classList.contains(); assert _serverSettings.sidebarOpen
  instead of localStorage values
- loadGridViewMode/saveGridViewMode tests: replace localStorage setup with
  _setServerSettings({gridViewMode: ...}); assert _serverSettings results
- cycleViewMode test: replace loadDisplaySettings/saveDisplaySettings with
  _setServerSettings/getDisplaySettings
- activityIndicator tests (7): replace _localStorageStore['muxplex.display']
  JSON.stringify() with app._setServerSettings({activityIndicator: ...})
- killSession test: increase substring limit from 500 to 600 chars

Part C: Add _getServerSettings test helper to app.js exports so tests can
read back _serverSettings state after sidebar/display mutations.
This commit is contained in:
Brian Krabach
2026-04-08 11:55:31 -07:00
parent 5da5fa8a45
commit 5f09d0b0f0
2 changed files with 87 additions and 77 deletions
+18 -10
View File
@@ -1462,7 +1462,7 @@ function applyFitLayout(grid) {
/** /**
* Cycle the dashboard view mode: auto → fit → auto. * Cycle the dashboard view mode: auto → fit → auto.
* Persists to localStorage and reapplies display settings. * Persists to server settings and reapplies display settings.
*/ */
function cycleViewMode() { function cycleViewMode() {
var ds = getDisplaySettings(); var ds = getDisplaySettings();
@@ -1522,7 +1522,7 @@ function applyDisplaySettings(ds) {
} }
/** /**
* Load grid view mode preference from display settings (localStorage). * Load grid view mode preference from display settings (server).
* Returns 'flat' as default. * Returns 'flat' as default.
* @returns {string} * @returns {string}
*/ */
@@ -1532,7 +1532,7 @@ function loadGridViewMode() {
} }
/** /**
* Save grid view mode preference to display settings (localStorage) and update _gridViewMode. * Save grid view mode preference to server settings and update _gridViewMode.
* @param {string} mode - The grid view mode to save. * @param {string} mode - The grid view mode to save.
*/ */
function saveGridViewMode(mode) { function saveGridViewMode(mode) {
@@ -2447,6 +2447,11 @@ function _setServerSettings(settings) {
_serverSettings = settings; _serverSettings = settings;
} }
/** Test-only: get _serverSettings. */
function _getServerSettings() {
return _serverSettings;
}
/** Test-only: get _gridViewMode. */ /** Test-only: get _gridViewMode. */
function _getGridViewMode() { function _getGridViewMode() {
return _gridViewMode; return _gridViewMode;
@@ -2471,8 +2476,12 @@ window.addEventListener('resize', function() {
} }
}); });
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', async function() {
initDeviceId(); initDeviceId();
// Load ALL settings (now includes display + sidebar) before first render
await loadServerSettings();
var _initDs = getDisplaySettings(); var _initDs = getDisplaySettings();
applyDisplaySettings(_initDs); applyDisplaySettings(_initDs);
_gridViewMode = loadGridViewMode(); _gridViewMode = loadGridViewMode();
@@ -2486,17 +2495,15 @@ document.addEventListener('DOMContentLoaded', () => {
document.addEventListener('touchstart', trackInteraction); document.addEventListener('touchstart', trackInteraction);
restoreState() restoreState()
.then(() => { .then(function() {
startPolling(); startPolling();
loadServerSettings().then(function() { updatePageTitle();
updatePageTitle();
});
startHeartbeat(); startHeartbeat();
bindStaticEventListeners(); bindStaticEventListeners();
}) })
.catch((err) => { .catch(function(err) {
console.error('[init] restoreState failed, retrying in 5s:', err); console.error('[init] restoreState failed, retrying in 5s:', err);
setTimeout(() => startPolling(), POLL_MS); setTimeout(function() { startPolling(); }, POLL_MS);
}); });
}); });
@@ -2581,6 +2588,7 @@ if (typeof module !== 'undefined' && module.exports) {
_setCurrentSessions, _setCurrentSessions,
_setViewMode, _setViewMode,
_setServerSettings, _setServerSettings,
_getServerSettings,
_getGridViewMode, _getGridViewMode,
_setGridViewMode, _setGridViewMode,
_setActiveFilterDevice, _setActiveFilterDevice,
+69 -67
View File
@@ -1773,7 +1773,7 @@ test('initSidebar defaults to open (removes sidebar--collapsed) on wide screens
}); });
test('initSidebar defaults to closed (adds sidebar--collapsed) on narrow screens when no stored value', () => { test('initSidebar defaults to closed (adds sidebar--collapsed) on narrow screens when no stored value', () => {
delete _localStorageStore['muxplex.sidebarOpen']; app._setServerSettings(null); // ensure no stored sidebarOpen value in server settings
const origInnerWidth = globalThis.window.innerWidth; const origInnerWidth = globalThis.window.innerWidth;
globalThis.window.innerWidth = 600; globalThis.window.innerWidth = 600;
@@ -1800,7 +1800,7 @@ test('initSidebar defaults to closed (adds sidebar--collapsed) on narrow screens
}); });
test('initSidebar respects stored value true regardless of screen width — even at 600px removes collapsed class', () => { test('initSidebar respects stored value true regardless of screen width — even at 600px removes collapsed class', () => {
_localStorageStore['muxplex.sidebarOpen'] = 'true'; app._setServerSettings({ sidebarOpen: true }); // stored value = true in server settings
const origInnerWidth = globalThis.window.innerWidth; const origInnerWidth = globalThis.window.innerWidth;
globalThis.window.innerWidth = 600; globalThis.window.innerWidth = 600;
@@ -1824,15 +1824,17 @@ test('initSidebar respects stored value true regardless of screen width — even
globalThis.document.getElementById = origGetById; globalThis.document.getElementById = origGetById;
globalThis.window.innerWidth = origInnerWidth; globalThis.window.innerWidth = origInnerWidth;
app._setServerSettings(null);
}); });
// ─── toggleSidebar ─────────────────────────────────────────────────────────── // ─── toggleSidebar ───────────────────────────────────────────────────────────
test('toggleSidebar persists state to localStorage — from true toggles to false', () => { test('toggleSidebar persists state to _serverSettings — from open toggles to closed', () => {
_localStorageStore['muxplex.sidebarOpen'] = 'true'; app._setServerSettings({}); // non-null so sidebarOpen assignment takes effect
// Sidebar is open (no sidebar--collapsed class) — contains returns false
const mockSidebar = { const mockSidebar = {
classList: { remove: () => {}, add: () => {} }, classList: { remove: () => {}, add: () => {}, contains: () => false },
}; };
const mockCollapseBtn = { textContent: '' }; const mockCollapseBtn = { textContent: '' };
const origGetById = globalThis.document.getElementById; const origGetById = globalThis.document.getElementById;
@@ -1844,17 +1846,19 @@ test('toggleSidebar persists state to localStorage — from true toggles to fals
app.toggleSidebar(); app.toggleSidebar();
assert.strictEqual(_localStorageStore['muxplex.sidebarOpen'], 'false', 'should persist false after toggling from true'); assert.strictEqual(app._getServerSettings().sidebarOpen, false, 'should set sidebarOpen=false in _serverSettings after toggling from open');
globalThis.document.getElementById = origGetById; globalThis.document.getElementById = origGetById;
app._setServerSettings(null);
}); });
test('toggleSidebar adds sidebar--collapsed class when closing (from open)', () => { test('toggleSidebar adds sidebar--collapsed class when closing (from open)', () => {
_localStorageStore['muxplex.sidebarOpen'] = 'true'; app._setServerSettings({}); // non-null so sidebarOpen assignment takes effect
const addedClasses = []; const addedClasses = [];
// Sidebar is open (no sidebar--collapsed class) — contains returns false
const mockSidebar = { const mockSidebar = {
classList: { remove: () => {}, add: (c) => addedClasses.push(c) }, classList: { remove: () => {}, add: (c) => addedClasses.push(c), contains: () => false },
}; };
const mockCollapseBtn = { textContent: '' }; const mockCollapseBtn = { textContent: '' };
const origGetById = globalThis.document.getElementById; const origGetById = globalThis.document.getElementById;
@@ -1869,14 +1873,16 @@ test('toggleSidebar adds sidebar--collapsed class when closing (from open)', ()
assert.ok(addedClasses.includes('sidebar--collapsed'), 'should add sidebar--collapsed class when closing'); assert.ok(addedClasses.includes('sidebar--collapsed'), 'should add sidebar--collapsed class when closing');
globalThis.document.getElementById = origGetById; globalThis.document.getElementById = origGetById;
app._setServerSettings(null);
}); });
test('toggleSidebar removes sidebar--collapsed class when opening (from closed) and sets localStorage to true', () => { test('toggleSidebar removes sidebar--collapsed class when opening (from closed) and persists to _serverSettings', () => {
_localStorageStore['muxplex.sidebarOpen'] = 'false'; app._setServerSettings({}); // non-null so sidebarOpen assignment takes effect
const removedClasses = []; const removedClasses = [];
// Sidebar is closed (has sidebar--collapsed class) — contains returns true
const mockSidebar = { const mockSidebar = {
classList: { remove: (c) => removedClasses.push(c), add: () => {} }, classList: { remove: (c) => removedClasses.push(c), add: () => {}, contains: (c) => c === 'sidebar--collapsed' },
}; };
const mockCollapseBtn = { textContent: '' }; const mockCollapseBtn = { textContent: '' };
const origGetById = globalThis.document.getElementById; const origGetById = globalThis.document.getElementById;
@@ -1889,9 +1895,10 @@ test('toggleSidebar removes sidebar--collapsed class when opening (from closed)
app.toggleSidebar(); app.toggleSidebar();
assert.ok(removedClasses.includes('sidebar--collapsed'), 'should remove sidebar--collapsed class when opening'); assert.ok(removedClasses.includes('sidebar--collapsed'), 'should remove sidebar--collapsed class when opening');
assert.strictEqual(_localStorageStore['muxplex.sidebarOpen'], 'true', 'should persist true after toggling from false'); assert.strictEqual(app._getServerSettings().sidebarOpen, true, 'should set sidebarOpen=true in _serverSettings after toggling from closed');
globalThis.document.getElementById = origGetById; globalThis.document.getElementById = origGetById;
app._setServerSettings(null);
}); });
// --- Guard: initSidebar and renderSidebar are exported and callable --- // --- Guard: initSidebar and renderSidebar are exported and callable ---
@@ -2685,51 +2692,47 @@ test('loadGridViewMode returns flat by default', () => {
assert.strictEqual(mode, 'flat', 'loadGridViewMode should return flat when no preference is set'); assert.strictEqual(mode, 'flat', 'loadGridViewMode should return flat when no preference is set');
}); });
test('loadGridViewMode reads from localStorage when scope is local', () => { test('loadGridViewMode reads gridViewMode from _serverSettings', () => {
// Set display settings with viewPreferenceScope 'local' and gridViewMode 'grouped' // gridViewMode is now stored in server settings
_localStorageStore = {}; _localStorageStore = {};
_localStorageStore['muxplex.display'] = JSON.stringify({ viewPreferenceScope: 'local', gridViewMode: 'grouped' }); app._setServerSettings({ gridViewMode: 'grouped' });
app._setServerSettings(null);
const mode = app.loadGridViewMode(); const mode = app.loadGridViewMode();
assert.strictEqual(mode, 'grouped', 'loadGridViewMode should return gridViewMode from localStorage when scope is local'); assert.strictEqual(mode, 'grouped', 'loadGridViewMode should return gridViewMode from _serverSettings');
// Cleanup // Cleanup
_localStorageStore = {}; app._setServerSettings(null);
}); });
test('loadGridViewMode reads from localStorage regardless of viewPreferenceScope', () => { test('loadGridViewMode reads gridViewMode from _serverSettings (not localStorage)', () => {
// viewPreferenceScope is no longer used — loadGridViewMode always reads from localStorage // viewPreferenceScope is removed — loadGridViewMode reads from _serverSettings via getDisplaySettings()
_localStorageStore = {}; _localStorageStore = {};
_localStorageStore['muxplex.display'] = JSON.stringify({ viewPreferenceScope: 'server', gridViewMode: 'filtered' }); app._setServerSettings({ gridViewMode: 'filtered' });
app._setServerSettings({ grid_view_mode: 'grouped' });
const mode = app.loadGridViewMode(); const mode = app.loadGridViewMode();
// Must return localStorage value ('filtered'), NOT server value ('grouped') // Must return _serverSettings value ('filtered')
assert.strictEqual(mode, 'filtered', 'loadGridViewMode should always return gridViewMode from localStorage'); assert.strictEqual(mode, 'filtered', 'loadGridViewMode should return gridViewMode from _serverSettings');
// Cleanup // Cleanup
_localStorageStore = {}; _localStorageStore = {};
app._setServerSettings(null); app._setServerSettings(null);
}); });
test('saveGridViewMode stores to localStorage when scope is local', () => { test('saveGridViewMode stores to _serverSettings', () => {
// Set scope to local (default) // gridViewMode is now stored in server settings
_localStorageStore = {}; _localStorageStore = {};
_localStorageStore['muxplex.display'] = JSON.stringify({ viewPreferenceScope: 'local' }); app._setServerSettings({});
app._setServerSettings(null);
app.saveGridViewMode('grouped'); app.saveGridViewMode('grouped');
// Verify _gridViewMode was updated // Verify _gridViewMode was updated
assert.strictEqual(app._getGridViewMode(), 'grouped', '_gridViewMode should be set to grouped'); assert.strictEqual(app._getGridViewMode(), 'grouped', '_gridViewMode should be set to grouped');
// Verify it was saved to localStorage display settings // Verify it was saved to _serverSettings
const saved = JSON.parse(_localStorageStore['muxplex.display'] || '{}'); assert.strictEqual(app._getServerSettings().gridViewMode, 'grouped', 'gridViewMode should be saved to _serverSettings');
assert.strictEqual(saved.gridViewMode, 'grouped', 'gridViewMode should be saved to localStorage display settings');
// Cleanup // Cleanup
_localStorageStore = {}; app._setServerSettings(null);
app._setGridViewMode('flat'); app._setGridViewMode('flat');
}); });
@@ -3078,20 +3081,21 @@ test('applyDisplaySettings does NOT handle compact mode (compact was removed)',
}); });
test('cycleViewMode cycles through auto -> fit -> auto (two modes, compact removed)', () => { test('cycleViewMode cycles through auto -> fit -> auto (two modes, compact removed)', () => {
// Reset display settings to auto // Reset server settings so viewMode starts at 'auto'
const ds = app.loadDisplaySettings(); app._setServerSettings({ viewMode: 'auto' });
ds.viewMode = 'auto';
app.saveDisplaySettings(ds);
// First cycle: auto -> fit // First cycle: auto -> fit
app.cycleViewMode(); app.cycleViewMode();
const ds1 = app.loadDisplaySettings(); const ds1 = app.getDisplaySettings();
assert.strictEqual(ds1.viewMode, 'fit', 'first cycle should go auto -> fit'); assert.strictEqual(ds1.viewMode, 'fit', 'first cycle should go auto -> fit');
// Second cycle: fit -> auto (wraps, compact is gone) // Second cycle: fit -> auto (wraps, compact is gone)
app.cycleViewMode(); app.cycleViewMode();
const ds2 = app.loadDisplaySettings(); const ds2 = app.getDisplaySettings();
assert.strictEqual(ds2.viewMode, 'auto', 'second cycle should wrap fit -> auto (only two modes)'); assert.strictEqual(ds2.viewMode, 'auto', 'second cycle should wrap fit -> auto (only two modes)');
// Cleanup
app._setServerSettings(null);
}); });
test('bindStaticEventListeners wires view-mode-btn click to cycleViewMode', () => { test('bindStaticEventListeners wires view-mode-btn click to cycleViewMode', () => {
@@ -3362,25 +3366,23 @@ test('HTML index.html has no setting-view-scope select element', () => {
assert.ok(!source.includes('setting-view-scope'), '#setting-view-scope must be removed from HTML'); assert.ok(!source.includes('setting-view-scope'), '#setting-view-scope must be removed from HTML');
}); });
test('loadGridViewMode always reads from localStorage (ignores viewPreferenceScope=server)', () => { test('loadGridViewMode always reads from _serverSettings (view scope removed, localStorage ignored)', () => {
// After removing view scope, loadGridViewMode should ALWAYS use localStorage regardless // After removing view scope, loadGridViewMode always reads from _serverSettings
_localStorageStore = {}; _localStorageStore = {};
_localStorageStore['muxplex.display'] = JSON.stringify({ viewPreferenceScope: 'server', gridViewMode: 'grouped' }); app._setServerSettings({ gridViewMode: 'grouped' });
app._setServerSettings({ grid_view_mode: 'filtered' });
const mode = app.loadGridViewMode(); const mode = app.loadGridViewMode();
// Must return the localStorage value ('grouped'), NOT the server value ('filtered') // Must return the _serverSettings value ('grouped')
assert.strictEqual(mode, 'grouped', 'loadGridViewMode must always read from localStorage — server scope removed'); assert.strictEqual(mode, 'grouped', 'loadGridViewMode must always read from _serverSettings — view scope removed');
// Cleanup // Cleanup
_localStorageStore = {}; _localStorageStore = {};
app._setServerSettings(null); app._setServerSettings(null);
}); });
test('saveGridViewMode always writes to localStorage (ignores viewPreferenceScope=server)', () => { test('saveGridViewMode always writes to _serverSettings (view scope removed)', () => {
_localStorageStore = {}; _localStorageStore = {};
_localStorageStore['muxplex.display'] = JSON.stringify({ viewPreferenceScope: 'server' }); app._setServerSettings({});
app._setServerSettings({ grid_view_mode: 'flat' });
const fetchCalls = []; const fetchCalls = [];
const origFetch = globalThis.fetch; const origFetch = globalThis.fetch;
@@ -3388,10 +3390,10 @@ test('saveGridViewMode always writes to localStorage (ignores viewPreferenceScop
app.saveGridViewMode('filtered'); app.saveGridViewMode('filtered');
// Must NOT call fetch (no server PATCH), must write to localStorage // Must call fetch to PATCH server settings
assert.strictEqual(fetchCalls.length, 0, 'saveGridViewMode must NOT call fetch server scope removed'); assert.ok(fetchCalls.length > 0, 'saveGridViewMode must call fetch to PATCH server settings');
const saved = JSON.parse(_localStorageStore['muxplex.display'] || '{}'); // Must write to _serverSettings
assert.strictEqual(saved.gridViewMode, 'filtered', 'gridViewMode must be saved to localStorage'); assert.strictEqual(app._getServerSettings().gridViewMode, 'filtered', 'gridViewMode must be saved to _serverSettings');
// Cleanup // Cleanup
globalThis.fetch = origFetch; globalThis.fetch = origFetch;
@@ -3556,12 +3558,12 @@ test('HTML index.html has setting-activity-indicator select element', () => {
}); });
test('buildTileHTML shows session-tile--edge-bell class when activityIndicator is dot (legacy test updated)', () => { test('buildTileHTML shows session-tile--edge-bell class when activityIndicator is dot (legacy test updated)', () => {
_localStorageStore['muxplex.display'] = JSON.stringify({ activityIndicator: 'dot' }); app._setServerSettings({ activityIndicator: 'dot' });
const session = { name: 's', bell: { unseen_count: 1, seen_at: null, last_fired_at: 100 }, snapshot: '' }; const session = { name: 's', bell: { unseen_count: 1, seen_at: null, last_fired_at: 100 }, snapshot: '' };
const html = app.buildTileHTML(session, 0, false); const html = app.buildTileHTML(session, 0, false);
assert.ok(html.includes('session-tile--edge-bell'), 'session-tile--edge-bell must appear when activityIndicator is dot'); assert.ok(html.includes('session-tile--edge-bell'), 'session-tile--edge-bell must appear when activityIndicator is dot');
assert.ok(!html.includes('session-tile--bell'), 'session-tile--bell (glow) must NOT appear when activityIndicator is dot only'); assert.ok(!html.includes('session-tile--bell'), 'session-tile--bell (glow) must NOT appear when activityIndicator is dot only');
_localStorageStore = {}; app._setServerSettings(null);
}); });
test('buildTileHTML shows both session-tile--bell and session-tile--edge-bell when activityIndicator is both (legacy test updated)', () => { test('buildTileHTML shows both session-tile--bell and session-tile--edge-bell when activityIndicator is both (legacy test updated)', () => {
@@ -3574,21 +3576,21 @@ test('buildTileHTML shows both session-tile--bell and session-tile--edge-bell wh
}); });
test('buildTileHTML omits all bell indicator classes when activityIndicator is none', () => { test('buildTileHTML omits all bell indicator classes when activityIndicator is none', () => {
_localStorageStore['muxplex.display'] = JSON.stringify({ activityIndicator: 'none' }); app._setServerSettings({ activityIndicator: 'none' });
const session = { name: 's', bell: { unseen_count: 1, seen_at: null, last_fired_at: 100 }, snapshot: '' }; const session = { name: 's', bell: { unseen_count: 1, seen_at: null, last_fired_at: 100 }, snapshot: '' };
const html = app.buildTileHTML(session, 0, false); const html = app.buildTileHTML(session, 0, false);
assert.ok(!html.includes('session-tile--bell'), 'session-tile--bell must NOT appear when activityIndicator is none'); assert.ok(!html.includes('session-tile--bell'), 'session-tile--bell must NOT appear when activityIndicator is none');
assert.ok(!html.includes('session-tile--edge-bell'), 'session-tile--edge-bell must NOT appear when activityIndicator is none'); assert.ok(!html.includes('session-tile--edge-bell'), 'session-tile--edge-bell must NOT appear when activityIndicator is none');
_localStorageStore = {}; app._setServerSettings(null);
}); });
test('buildTileHTML omits session-tile--edge-bell when activityIndicator is glow (glow only, no edge bar)', () => { test('buildTileHTML omits session-tile--edge-bell when activityIndicator is glow (glow only, no edge bar)', () => {
_localStorageStore['muxplex.display'] = JSON.stringify({ activityIndicator: 'glow' }); app._setServerSettings({ activityIndicator: 'glow' });
const session = { name: 's', bell: { unseen_count: 1, seen_at: null, last_fired_at: 100 }, snapshot: '' }; const session = { name: 's', bell: { unseen_count: 1, seen_at: null, last_fired_at: 100 }, snapshot: '' };
const html = app.buildTileHTML(session, 0, false); const html = app.buildTileHTML(session, 0, false);
assert.ok(html.includes('session-tile--bell'), 'session-tile--bell must appear when activityIndicator is glow'); assert.ok(html.includes('session-tile--bell'), 'session-tile--bell must appear when activityIndicator is glow');
assert.ok(!html.includes('session-tile--edge-bell'), 'session-tile--edge-bell must NOT appear when activityIndicator is glow'); assert.ok(!html.includes('session-tile--edge-bell'), 'session-tile--edge-bell must NOT appear when activityIndicator is glow');
_localStorageStore = {}; app._setServerSettings(null);
}); });
test('buildTileHTML adds session-tile--bell when activityIndicator is glow', () => { test('buildTileHTML adds session-tile--bell when activityIndicator is glow', () => {
@@ -3608,11 +3610,11 @@ test('buildTileHTML adds session-tile--bell when activityIndicator is both', ()
}); });
test('buildTileHTML omits session-tile--bell when activityIndicator is none', () => { test('buildTileHTML omits session-tile--bell when activityIndicator is none', () => {
_localStorageStore['muxplex.display'] = JSON.stringify({ activityIndicator: 'none' }); app._setServerSettings({ activityIndicator: 'none' });
const session = { name: 's', bell: { unseen_count: 1, seen_at: null, last_fired_at: 100 }, snapshot: '' }; const session = { name: 's', bell: { unseen_count: 1, seen_at: null, last_fired_at: 100 }, snapshot: '' };
const html = app.buildTileHTML(session, 0, false); const html = app.buildTileHTML(session, 0, false);
assert.ok(!html.includes('session-tile--bell'), 'session-tile--bell must NOT appear when activityIndicator is none'); assert.ok(!html.includes('session-tile--bell'), 'session-tile--bell must NOT appear when activityIndicator is none');
_localStorageStore = {}; app._setServerSettings(null);
}); });
test('CSS style.css has #setting-device-name max-width rule', () => { test('CSS style.css has #setting-device-name max-width rule', () => {
@@ -3722,19 +3724,19 @@ test('buildTileHTML adds session-tile--edge-bell class when activityIndicator is
}); });
test('buildTileHTML does NOT add session-tile--edge-bell when activityIndicator is glow', () => { test('buildTileHTML does NOT add session-tile--edge-bell when activityIndicator is glow', () => {
_localStorageStore['muxplex.display'] = JSON.stringify({ activityIndicator: 'glow' }); app._setServerSettings({ activityIndicator: 'glow' });
const session = { name: 's', bell: { unseen_count: 1, seen_at: null, last_fired_at: 100 }, snapshot: '' }; const session = { name: 's', bell: { unseen_count: 1, seen_at: null, last_fired_at: 100 }, snapshot: '' };
const html = app.buildTileHTML(session, 0, false); const html = app.buildTileHTML(session, 0, false);
assert.ok(!html.includes('session-tile--edge-bell'), 'session-tile--edge-bell must NOT appear when activityIndicator is glow'); assert.ok(!html.includes('session-tile--edge-bell'), 'session-tile--edge-bell must NOT appear when activityIndicator is glow');
_localStorageStore = {}; app._setServerSettings(null);
}); });
test('buildTileHTML does NOT add session-tile--edge-bell when activityIndicator is none', () => { test('buildTileHTML does NOT add session-tile--edge-bell when activityIndicator is none', () => {
_localStorageStore['muxplex.display'] = JSON.stringify({ activityIndicator: 'none' }); app._setServerSettings({ activityIndicator: 'none' });
const session = { name: 's', bell: { unseen_count: 1, seen_at: null, last_fired_at: 100 }, snapshot: '' }; const session = { name: 's', bell: { unseen_count: 1, seen_at: null, last_fired_at: 100 }, snapshot: '' };
const html = app.buildTileHTML(session, 0, false); const html = app.buildTileHTML(session, 0, false);
assert.ok(!html.includes('session-tile--edge-bell'), 'session-tile--edge-bell must NOT appear when activityIndicator is none'); assert.ok(!html.includes('session-tile--edge-bell'), 'session-tile--edge-bell must NOT appear when activityIndicator is none');
_localStorageStore = {}; app._setServerSettings(null);
}); });
test('buildSidebarHTML has single-line header with name, badge, and delete button', () => { test('buildSidebarHTML has single-line header with name, badge, and delete button', () => {
@@ -3774,11 +3776,11 @@ test('buildSidebarHTML adds sidebar-item--edge-bell when activityIndicator is bo
}); });
test('buildSidebarHTML does NOT add sidebar-item--edge-bell when activityIndicator is glow', () => { test('buildSidebarHTML does NOT add sidebar-item--edge-bell when activityIndicator is glow', () => {
_localStorageStore['muxplex.display'] = JSON.stringify({ activityIndicator: 'glow' }); app._setServerSettings({ activityIndicator: 'glow' });
const session = { name: 's', snapshot: '', bell: { unseen_count: 2 } }; const session = { name: 's', snapshot: '', bell: { unseen_count: 2 } };
const html = app.buildSidebarHTML(session, ''); const html = app.buildSidebarHTML(session, '');
assert.ok(!html.includes('sidebar-item--edge-bell'), 'sidebar-item--edge-bell must NOT appear when activityIndicator is glow'); assert.ok(!html.includes('sidebar-item--edge-bell'), 'sidebar-item--edge-bell must NOT appear when activityIndicator is glow');
_localStorageStore = {}; app._setServerSettings(null);
}); });
test('buildSidebarHTML does NOT include tile-bell-dot in HTML', () => { test('buildSidebarHTML does NOT include tile-bell-dot in HTML', () => {
@@ -3963,9 +3965,9 @@ test('remote instance debounced input listener selector includes .settings-remot
test('killSession closes active session and returns to dashboard', () => { test('killSession closes active session and returns to dashboard', () => {
const source = fs.readFileSync(new URL('../app.js', import.meta.url), 'utf8'); const source = fs.readFileSync(new URL('../app.js', import.meta.url), 'utf8');
// Find killSession function body // Find killSession function body (use 600 chars to ensure closeSession call is included)
const fnStart = source.indexOf('function killSession'); const fnStart = source.indexOf('function killSession');
const fnBody = source.substring(fnStart, fnStart + 500); const fnBody = source.substring(fnStart, fnStart + 600);
assert.ok(fnBody.includes('_viewingSession'), 'killSession must check if deleted session is the active one'); assert.ok(fnBody.includes('_viewingSession'), 'killSession must check if deleted session is the active one');
assert.ok(fnBody.includes('closeSession'), 'killSession must call closeSession when deleting the active session'); assert.ok(fnBody.includes('closeSession'), 'killSession must call closeSession when deleting the active session');
}); });