feat: add view dropdown trigger and menu to app header (task-4)

- Add #view-dropdown-trigger button between wordmark and header-actions
- Add #view-dropdown-menu container with role='menu' and hidden class
- Add #view-dropdown-label with default text 'All Sessions'
- Add caret span with aria-hidden='true'
- Trigger has aria-haspopup='true', aria-expanded='false', aria-controls attributes
- All wrapped in .view-dropdown div with id='view-dropdown'

Tests added:
- test_view_dropdown_trigger_exists
- test_view_dropdown_container_exists
- test_view_dropdown_trigger_has_aria
- test_view_dropdown_menu_has_role_menu

All 93 tests pass (4 new + 89 existing).
This commit is contained in:
Brian Krabach
2026-04-15 17:23:00 -07:00
parent ecca48a1ad
commit 7f42d6cbe2
2 changed files with 52 additions and 0 deletions
+7
View File
@@ -19,6 +19,13 @@
<div id="view-overview" class="view view--active">
<header class="app-header">
<h1 class="app-wordmark"><img src="/wordmark-on-dark.svg" alt="muxplex" height="24" /></h1>
<div class="view-dropdown" id="view-dropdown">
<button id="view-dropdown-trigger" class="view-dropdown__trigger" aria-haspopup="true" aria-expanded="false" aria-controls="view-dropdown-menu">
<span id="view-dropdown-label">All Sessions</span>
<span class="view-dropdown__caret" aria-hidden="true">&#9662;</span>
</button>
<div id="view-dropdown-menu" class="view-dropdown__menu hidden" role="menu" aria-label="Switch view"></div>
</div>
<div class="header-actions">
<button id="new-session-btn" class="header-btn" aria-label="New session">+</button>
<button id="view-mode-btn" class="header-btn" aria-label="Toggle view mode" title="View: auto">&#9638;</button>
+45
View File
@@ -1419,3 +1419,48 @@ def test_html_has_search_bar() -> None:
html = read_html()
assert "terminal-search-bar" in html, "Must have #terminal-search-bar element"
assert "terminal-search-input" in html, "Must have #terminal-search-input element"
# ============================================================
# Header Dropdown (task-4)
# ============================================================
def test_view_dropdown_trigger_exists() -> None:
"""#view-dropdown-trigger element must exist in the header."""
soup = _SOUP
trigger = soup.find(id="view-dropdown-trigger")
assert trigger is not None, "Missing #view-dropdown-trigger"
assert trigger.name == "button", (
f"#view-dropdown-trigger must be a <button>, got: {trigger.name}"
)
def test_view_dropdown_container_exists() -> None:
"""#view-dropdown-menu container must exist in the header."""
soup = _SOUP
menu = soup.find(id="view-dropdown-menu")
assert menu is not None, "Missing #view-dropdown-menu"
def test_view_dropdown_trigger_has_aria() -> None:
"""#view-dropdown-trigger must have aria-haspopup='true' and aria-expanded='false'."""
soup = _SOUP
trigger = soup.find(id="view-dropdown-trigger")
assert trigger is not None, "Missing #view-dropdown-trigger"
assert trigger.get("aria-haspopup") == "true", (
f"#view-dropdown-trigger must have aria-haspopup='true', got: {trigger.get('aria-haspopup')!r}"
)
assert trigger.get("aria-expanded") == "false", (
f"#view-dropdown-trigger must have aria-expanded='false', got: {trigger.get('aria-expanded')!r}"
)
def test_view_dropdown_menu_has_role_menu() -> None:
"""#view-dropdown-menu must have role='menu'."""
soup = _SOUP
menu = soup.find(id="view-dropdown-menu")
assert menu is not None, "Missing #view-dropdown-menu"
assert menu.get("role") == "menu", (
f"#view-dropdown-menu must have role='menu', got: {menu.get('role')!r}"
)