From 4ed8303b6e4e5377c7e0a2c77b6cf77952860ad3 Mon Sep 17 00:00:00 2001 From: Brian Krabach Date: Mon, 30 Mar 2026 23:22:55 -0700 Subject: [PATCH] feat: update getVisibleSessions to only hide local sessions by name - Replace getVisibleSessions filter logic: only hide sessions where sourceUrl is empty/absent AND name matches hidden_sessions list - Remote sessions with the same name as a hidden local session now remain visible (task-7 spec requirement) - Export getVisibleSessions in module.exports so tests can access it Tests added: - 'getVisibleSessions exported and filters hidden sessions': verifies export and that local hidden sessions are filtered - 'getVisibleSessions hides local sessions by name but not remote sessions with same name': verifies remote sessions survive All 155 tests pass. --- muxplex/frontend/app.js | 12 +++++-- muxplex/frontend/tests/test_app.mjs | 53 +++++++++++++++++++++++++++++ 2 files changed, 63 insertions(+), 2 deletions(-) diff --git a/muxplex/frontend/app.js b/muxplex/frontend/app.js index 2b3635b..f2eb97e 100644 --- a/muxplex/frontend/app.js +++ b/muxplex/frontend/app.js @@ -520,13 +520,20 @@ function buildSidebarHTML(session, currentSession) { /** * Returns sessions with hidden session names removed. + * Only hides LOCAL sessions (those with empty/absent sourceUrl) matching the + * hidden_sessions list. Remote sessions with the same name remain visible. * Consolidates the hidden-session filter used by all render paths. * @param {object[]} sessions * @returns {object[]} */ function getVisibleSessions(sessions) { - const hidden = (_serverSettings && _serverSettings.hidden_sessions) || []; - return (sessions || []).filter((s) => !hidden.includes(s.name)); + var hidden = (_serverSettings && _serverSettings.hidden_sessions) || []; + return (sessions || []).filter(function(s) { + if (hidden.length > 0 && (!s.sourceUrl) && hidden.includes(s.name)) { + return false; + } + return true; + }); } /** @@ -1821,6 +1828,7 @@ if (typeof module !== 'undefined' && module.exports) { escapeHtml, buildTileHTML, buildSidebarHTML, + getVisibleSessions, renderSidebar, initSidebar, toggleSidebar, diff --git a/muxplex/frontend/tests/test_app.mjs b/muxplex/frontend/tests/test_app.mjs index cc26083..339055a 100644 --- a/muxplex/frontend/tests/test_app.mjs +++ b/muxplex/frontend/tests/test_app.mjs @@ -2285,6 +2285,59 @@ test('pollSessions sets auth_required on 401 response', async () => { app._setSources([]); }); +// --- getVisibleSessions (task-7) --- + +test('getVisibleSessions exported and filters hidden sessions', () => { + // Verify getVisibleSessions is exported as a function + assert.strictEqual(typeof app.getVisibleSessions, 'function', 'getVisibleSessions should be exported as a function'); + + // Set up server settings with hidden_sessions + app._setServerSettings({ hidden_sessions: ['secret', 'hidden-local'] }); + + // Local sessions (no sourceUrl) matching hidden list should be filtered + const sessions = [ + { name: 'visible', sourceUrl: '' }, + { name: 'secret', sourceUrl: '' }, // local, should be hidden + { name: 'hidden-local', sourceUrl: '' }, // local, should be hidden + { name: 'other', sourceUrl: '' }, + ]; + + const result = app.getVisibleSessions(sessions); + assert.strictEqual(result.length, 2, 'should hide 2 local sessions matching the hidden list'); + assert.ok(result.some((s) => s.name === 'visible'), 'visible should remain'); + assert.ok(result.some((s) => s.name === 'other'), 'other should remain'); + assert.ok(!result.some((s) => s.name === 'secret'), 'secret (local) should be hidden'); + assert.ok(!result.some((s) => s.name === 'hidden-local'), 'hidden-local should be hidden'); + + // Clean up + app._setServerSettings(null); +}); + +test('getVisibleSessions hides local sessions by name but not remote sessions with same name', () => { + // Set server settings with a session name that exists both locally and remotely + app._setServerSettings({ hidden_sessions: ['shared-name'] }); + + const sessions = [ + { name: 'shared-name', sourceUrl: '' }, // local — should be hidden + { name: 'shared-name', sourceUrl: 'https://remote.example.com' }, // remote — should remain visible + { name: 'another', sourceUrl: '' }, // local, not in hidden list — should remain + ]; + + const result = app.getVisibleSessions(sessions); + assert.strictEqual(result.length, 2, 'should show 2 sessions (remote + another)'); + // The remote one should survive + const remote = result.find((s) => s.sourceUrl === 'https://remote.example.com'); + assert.ok(remote, 'remote session with same name should not be hidden'); + assert.strictEqual(remote.name, 'shared-name', 'remote session name should be shared-name'); + // The local one should be hidden + assert.ok(!result.some((s) => s.sourceUrl === '' && s.name === 'shared-name'), 'local session with hidden name should be removed'); + // another should remain + assert.ok(result.some((s) => s.name === 'another'), 'another should remain visible'); + + // Clean up + app._setServerSettings(null); +}); + test('pollSessions sets unreachable and applies exponential backoff on network error', async () => { const mockStatusEl = { textContent: '', className: '' }; const mockGrid = { innerHTML: '' };