feat: rewrite getVisibleSessions() to honor active view (task-2)

- Add _activeView state variable with default 'all' in App state section
- Rewrite getVisibleSessions() to filter by active view:
  - 'all' view: excludes hidden sessions
  - 'hidden' view: shows only hidden sessions
  - user view: filters to view's sessions list using sessionKey
  - Falls back to 'all' if active view no longer exists
- Add _getActiveView() and _setActiveView() test helpers
- Export new test helpers in window.MuxplexApp (module.exports)
- Add 6 new tests covering all acceptance criteria

Tests added:
- test_active_view_state_variable_exists
- test_get_visible_sessions_all_view_excludes_hidden
- test_get_visible_sessions_hidden_view_shows_hidden
- test_get_visible_sessions_user_view_filters_by_session_key
- test_get_active_view_helper_exported
- test_set_active_view_helper_exported

All 220 tests pass.
This commit is contained in:
Brian Krabach
2026-04-15 17:08:04 -07:00
parent acdfc9bafd
commit c3eb714977
2 changed files with 124 additions and 5 deletions
+41 -5
View File
@@ -137,6 +137,7 @@ let _settingsOpen = false;
let _serverSettings = null;
let _gridViewMode = 'flat';
let _activeFilterDevice = 'all';
let _activeView = 'all';
const DISPLAY_DEFAULTS = {
fontSize: 14,
hoverPreviewDelay: 1500,
@@ -531,18 +532,45 @@ function buildStatusTileHTML(deviceName, statusText, statusClass) {
}
/**
* Returns sessions with hidden session names removed.
* Hides any session whose name appears in the hidden_sessions list,
* regardless of whether it is local or remote (federated).
* Consolidates the hidden-session filter used by all render paths.
* Returns sessions filtered by the active view.
*
* - 'all' view: excludes hidden sessions (sessions in hidden_sessions list)
* - 'hidden' view: shows only hidden sessions
* - user view: shows only sessions whose sessionKey is in that view's sessions list
*
* Status entries (unreachable, auth_failed, empty) are always excluded —
* they are rendered separately as status tiles.
*
* Falls back to 'all' behaviour if the active view no longer exists.
*
* @param {object[]} sessions
* @returns {object[]}
*/
function getVisibleSessions(sessions) {
var hidden = (_serverSettings && _serverSettings.hidden_sessions) || [];
var views = (_serverSettings && _serverSettings.views) || [];
var view = _resolveActiveView(_activeView, views);
return (sessions || []).filter(function(s) {
// Skip status entries (unreachable, auth_failed) — rendered separately as status tiles
// Skip status entries (unreachable, auth_failed, empty) — rendered separately as status tiles
if (s.status) return false;
if (view === 'hidden') {
// 'hidden' view: only show sessions that are in the hidden list
return hidden.length > 0 && (hidden.includes(s.sessionKey || s.name) || hidden.includes(s.name));
}
if (view !== 'all') {
// User-defined view: show only sessions whose sessionKey is in this view's sessions list
var userView = views.find(function(v) { return v.name === view; });
if (userView) {
var viewSessions = userView.sessions || [];
return viewSessions.includes(s.sessionKey || s.name) || viewSessions.includes(s.name);
}
// View no longer exists — fall through to 'all' behaviour
}
// 'all' view: exclude hidden sessions
if (hidden.length > 0 && (hidden.includes(s.sessionKey || s.name) || hidden.includes(s.name))) {
return false;
}
@@ -2499,6 +2527,12 @@ function _setActiveFilterDevice(device) {
_activeFilterDevice = device;
}
/** Test-only: get current _activeView value. */
function _getActiveView() { return _activeView; }
/** Test-only: set _activeView directly. */
function _setActiveView(view) { _activeView = view; }
// Recalculate fit layout on window resize
window.addEventListener('resize', function() {
var ds = getDisplaySettings();
@@ -2625,5 +2659,7 @@ if (typeof module !== 'undefined' && module.exports) {
_getGridViewMode,
_setGridViewMode,
_setActiveFilterDevice,
_getActiveView,
_setActiveView,
};
}
+83
View File
@@ -3010,3 +3010,86 @@ def test_settings_hidden_sessions_uses_session_key() -> None:
"(with s.name fallback) as the checkbox value so remote sessions are "
"stored in device_id:name format in hidden_sessions"
)
# ---------------------------------------------------------------------------
# Active view state variable and getVisibleSessions (task-2-active-view)
# ---------------------------------------------------------------------------
def test_active_view_state_variable_exists() -> None:
"""_activeView state variable must be declared with default 'all'."""
assert "let _activeView = 'all'" in _JS, (
"let _activeView = 'all' must be declared in the App state section of app.js"
)
def test_get_visible_sessions_all_view_excludes_hidden() -> None:
"""getVisibleSessions must reference _activeView to honour the active view."""
match = re.search(
r"function getVisibleSessions\(sessions\)\s*\{(.*?)\n\}",
_JS,
re.DOTALL,
)
assert match, "getVisibleSessions function not found"
body = match.group(1)
assert "_activeView" in body, (
"getVisibleSessions must check _activeView to filter sessions by view"
)
def test_get_visible_sessions_hidden_view_shows_hidden() -> None:
"""getVisibleSessions must handle the 'hidden' view case."""
match = re.search(
r"function getVisibleSessions\(sessions\)\s*\{(.*?)\n\}",
_JS,
re.DOTALL,
)
assert match, "getVisibleSessions function not found"
body = match.group(1)
assert "'hidden'" in body or '"hidden"' in body, (
"getVisibleSessions must explicitly handle the 'hidden' view (show only hidden sessions)"
)
def test_get_visible_sessions_user_view_filters_by_session_key() -> None:
"""getVisibleSessions must use sessionKey when filtering for a user-defined view."""
match = re.search(
r"function getVisibleSessions\(sessions\)\s*\{(.*?)\n\}",
_JS,
re.DOTALL,
)
assert match, "getVisibleSessions function not found"
body = match.group(1)
assert "sessionKey" in body, (
"getVisibleSessions must use sessionKey to match sessions against the user view's "
"sessions list"
)
def test_get_active_view_helper_exported() -> None:
"""window.MuxplexApp must export _getActiveView test helper."""
match = re.search(
r"module\.exports\s*=\s*\{(.*?)\};",
_JS,
re.DOTALL,
)
assert match, "module.exports block not found"
exports = match.group(1)
assert "_getActiveView" in exports, (
"module.exports must export _getActiveView test helper"
)
def test_set_active_view_helper_exported() -> None:
"""window.MuxplexApp must export _setActiveView test helper."""
match = re.search(
r"module\.exports\s*=\s*\{(.*?)\};",
_JS,
re.DOTALL,
)
assert match, "module.exports block not found"
exports = match.group(1)
assert "_setActiveView" in exports, (
"module.exports must export _setActiveView test helper"
)