diff --git a/templates/index.html b/templates/index.html index 249f2b4..d3877d0 100644 --- a/templates/index.html +++ b/templates/index.html @@ -4162,6 +4162,9 @@ // Initialize dropdown nav active state updateDropdownActiveState(); + // Restore nav group open/closed state from localStorage + initNavGroupState(); + // Start SDR device status polling startSdrStatusPolling(); @@ -4189,6 +4192,45 @@ if (!isOpen) { dropdown.classList.add('open'); } + saveNavGroupState(); + } + + function initNavGroupState() { + const NAV_STATE_KEY = 'intercept_nav_groups'; + let savedState = {}; + try { + savedState = JSON.parse(localStorage.getItem(NAV_STATE_KEY) || '{}'); + } catch (e) { + savedState = {}; + } + + document.querySelectorAll('.mode-nav-dropdown[data-group]').forEach(dropdown => { + const group = dropdown.dataset.group; + // If saved state says closed AND this group has no active item, close it + if (savedState[group] === false) { + const hasActive = dropdown.classList.contains('has-active'); + if (!hasActive) { + dropdown.classList.remove('open'); + const btn = dropdown.querySelector('.mode-nav-dropdown-btn'); + if (btn) btn.setAttribute('aria-expanded', 'false'); + } + } else if (savedState[group] === true) { + dropdown.classList.add('open'); + const btn = dropdown.querySelector('.mode-nav-dropdown-btn'); + if (btn) btn.setAttribute('aria-expanded', 'true'); + } + }); + } + + function saveNavGroupState() { + const NAV_STATE_KEY = 'intercept_nav_groups'; + const state = {}; + document.querySelectorAll('.mode-nav-dropdown[data-group]').forEach(dropdown => { + state[dropdown.dataset.group] = dropdown.classList.contains('open'); + }); + try { + localStorage.setItem(NAV_STATE_KEY, JSON.stringify(state)); + } catch (e) { /* storage full or unavailable */ } } function closeAllDropdowns() { diff --git a/tests/test_nav_state.py b/tests/test_nav_state.py new file mode 100644 index 0000000..d8a858d --- /dev/null +++ b/tests/test_nav_state.py @@ -0,0 +1,25 @@ +"""Tests for nav group localStorage persistence (JS logic verified via structure check).""" + + +def _logged_in_get(client, path): + """Make a GET request with a pre-seeded logged-in session.""" + with client.session_transaction() as sess: + sess["logged_in"] = True + return client.get(path) + + +def test_index_page_includes_nav_state_init(client): + """nav group init function must be present in the index page.""" + resp = _logged_in_get(client, "/") + assert resp.status_code == 200 + html = resp.data.decode() + assert "initNavGroupState" in html + assert "localStorage" in html + + +def test_nav_groups_have_data_group_attributes(client): + """Each nav group must have a data-group attribute for state keying.""" + resp = _logged_in_get(client, "/") + html = resp.data.decode() + for group in ["signals", "tracking", "space", "wireless", "intel"]: + assert f'data-group="{group}"' in html, f"Missing data-group={group}"