fix: rename data-panel to data-tab on settings panels to match switchSettingsTab()
switchSettingsTab() in app.js reads panel.dataset.tab (data-tab attribute). The four .settings-panel elements in index.html were using data-panel instead, causing all panels to get hidden on every tab click — the settings dialog rendered as a blank content area after any tab interaction. Fix: Change data-panel="..." to data-tab="..." on all four panel divs. Tests added: - test_html_settings_panels_use_data_tab: verifies each .settings-panel uses data-tab and not data-panel - test_html_settings_tab_panel_data_tab_alignment: cross-checks that every tab button data-tab value has a matching .settings-panel data-tab value, preventing this class of HTML/JS attribute mismatch from recurring
This commit is contained in:
@@ -80,7 +80,7 @@
|
|||||||
<button class="settings-tab" data-tab="new-session">New Session</button>
|
<button class="settings-tab" data-tab="new-session">New Session</button>
|
||||||
</nav>
|
</nav>
|
||||||
<div class="settings-content">
|
<div class="settings-content">
|
||||||
<div class="settings-panel" data-panel="display">
|
<div class="settings-panel" data-tab="display">
|
||||||
<div class="settings-field">
|
<div class="settings-field">
|
||||||
<label class="settings-label" for="setting-font-size">Font Size</label>
|
<label class="settings-label" for="setting-font-size">Font Size</label>
|
||||||
<select id="setting-font-size" class="settings-select">
|
<select id="setting-font-size" class="settings-select">
|
||||||
@@ -111,9 +111,9 @@
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="settings-panel hidden" data-panel="sessions"></div>
|
<div class="settings-panel hidden" data-tab="sessions"></div>
|
||||||
<div class="settings-panel hidden" data-panel="notifications"></div>
|
<div class="settings-panel hidden" data-tab="notifications"></div>
|
||||||
<div class="settings-panel hidden" data-panel="new-session"></div>
|
<div class="settings-panel hidden" data-tab="new-session"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</dialog>
|
</dialog>
|
||||||
|
|||||||
@@ -340,7 +340,9 @@ def test_html_new_session_btn() -> None:
|
|||||||
btn = soup.find(id="new-session-btn")
|
btn = soup.find(id="new-session-btn")
|
||||||
assert btn is not None, "Missing #new-session-btn"
|
assert btn is not None, "Missing #new-session-btn"
|
||||||
classes = btn.get("class") or []
|
classes = btn.get("class") or []
|
||||||
assert "header-btn" in classes, f"#new-session-btn must have class 'header-btn', has: {classes}"
|
assert "header-btn" in classes, (
|
||||||
|
f"#new-session-btn must have class 'header-btn', has: {classes}"
|
||||||
|
)
|
||||||
# Must be inside header-actions
|
# Must be inside header-actions
|
||||||
header_actions = soup.find(class_="header-actions")
|
header_actions = soup.find(class_="header-actions")
|
||||||
assert header_actions is not None, "Missing .header-actions"
|
assert header_actions is not None, "Missing .header-actions"
|
||||||
@@ -355,7 +357,9 @@ def test_html_settings_btn() -> None:
|
|||||||
btn = soup.find(id="settings-btn")
|
btn = soup.find(id="settings-btn")
|
||||||
assert btn is not None, "Missing #settings-btn"
|
assert btn is not None, "Missing #settings-btn"
|
||||||
classes = btn.get("class") or []
|
classes = btn.get("class") or []
|
||||||
assert "header-btn" in classes, f"#settings-btn must have class 'header-btn', has: {classes}"
|
assert "header-btn" in classes, (
|
||||||
|
f"#settings-btn must have class 'header-btn', has: {classes}"
|
||||||
|
)
|
||||||
# Must be inside header-actions
|
# Must be inside header-actions
|
||||||
header_actions = soup.find(class_="header-actions")
|
header_actions = soup.find(class_="header-actions")
|
||||||
assert header_actions is not None, "Missing .header-actions"
|
assert header_actions is not None, "Missing .header-actions"
|
||||||
@@ -370,7 +374,9 @@ def test_html_settings_btn_expanded() -> None:
|
|||||||
btn = soup.find(id="settings-btn-expanded")
|
btn = soup.find(id="settings-btn-expanded")
|
||||||
assert btn is not None, "Missing #settings-btn-expanded"
|
assert btn is not None, "Missing #settings-btn-expanded"
|
||||||
classes = btn.get("class") or []
|
classes = btn.get("class") or []
|
||||||
assert "header-btn" in classes, f"#settings-btn-expanded must have class 'header-btn', has: {classes}"
|
assert "header-btn" in classes, (
|
||||||
|
f"#settings-btn-expanded must have class 'header-btn', has: {classes}"
|
||||||
|
)
|
||||||
# Must be inside expanded-header
|
# Must be inside expanded-header
|
||||||
header = soup.find("header", class_="expanded-header")
|
header = soup.find("header", class_="expanded-header")
|
||||||
assert header is not None, "Missing header.expanded-header"
|
assert header is not None, "Missing header.expanded-header"
|
||||||
@@ -382,8 +388,12 @@ def test_html_settings_btn_expanded() -> None:
|
|||||||
el.get("id") for el in header.children if isinstance(el, Tag)
|
el.get("id") for el in header.children if isinstance(el, Tag)
|
||||||
]
|
]
|
||||||
header_children_ids = [i for i in header_children_ids if i]
|
header_children_ids = [i for i in header_children_ids if i]
|
||||||
assert "expanded-session-name" in header_children_ids, "#expanded-session-name must be in expanded-header"
|
assert "expanded-session-name" in header_children_ids, (
|
||||||
assert "settings-btn-expanded" in header_children_ids, "#settings-btn-expanded must be in expanded-header"
|
"#expanded-session-name must be in expanded-header"
|
||||||
|
)
|
||||||
|
assert "settings-btn-expanded" in header_children_ids, (
|
||||||
|
"#settings-btn-expanded must be in expanded-header"
|
||||||
|
)
|
||||||
name_idx = header_children_ids.index("expanded-session-name")
|
name_idx = header_children_ids.index("expanded-session-name")
|
||||||
settings_idx = header_children_ids.index("settings-btn-expanded")
|
settings_idx = header_children_ids.index("settings-btn-expanded")
|
||||||
assert name_idx < settings_idx, (
|
assert name_idx < settings_idx, (
|
||||||
@@ -397,8 +407,12 @@ def test_html_settings_backdrop() -> None:
|
|||||||
el = soup.find(id="settings-backdrop")
|
el = soup.find(id="settings-backdrop")
|
||||||
assert el is not None, "Missing #settings-backdrop"
|
assert el is not None, "Missing #settings-backdrop"
|
||||||
classes = el.get("class") or []
|
classes = el.get("class") or []
|
||||||
assert "settings-backdrop" in classes, f"#settings-backdrop must have class 'settings-backdrop', has: {classes}"
|
assert "settings-backdrop" in classes, (
|
||||||
assert "hidden" in classes, f"#settings-backdrop must have class 'hidden', has: {classes}"
|
f"#settings-backdrop must have class 'settings-backdrop', has: {classes}"
|
||||||
|
)
|
||||||
|
assert "hidden" in classes, (
|
||||||
|
f"#settings-backdrop must have class 'hidden', has: {classes}"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_html_settings_dialog() -> None:
|
def test_html_settings_dialog() -> None:
|
||||||
@@ -406,9 +420,13 @@ def test_html_settings_dialog() -> None:
|
|||||||
soup = _SOUP
|
soup = _SOUP
|
||||||
el = soup.find(id="settings-dialog")
|
el = soup.find(id="settings-dialog")
|
||||||
assert el is not None, "Missing #settings-dialog"
|
assert el is not None, "Missing #settings-dialog"
|
||||||
assert el.name == "dialog", f"#settings-dialog must be a <dialog> element, got: {el.name}"
|
assert el.name == "dialog", (
|
||||||
|
f"#settings-dialog must be a <dialog> element, got: {el.name}"
|
||||||
|
)
|
||||||
classes = el.get("class") or []
|
classes = el.get("class") or []
|
||||||
assert "settings-dialog" in classes, f"#settings-dialog must have class 'settings-dialog', has: {classes}"
|
assert "settings-dialog" in classes, (
|
||||||
|
f"#settings-dialog must have class 'settings-dialog', has: {classes}"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_html_settings_tabs() -> None:
|
def test_html_settings_tabs() -> None:
|
||||||
@@ -417,11 +435,15 @@ def test_html_settings_tabs() -> None:
|
|||||||
dialog = soup.find(id="settings-dialog")
|
dialog = soup.find(id="settings-dialog")
|
||||||
assert dialog is not None, "Missing #settings-dialog"
|
assert dialog is not None, "Missing #settings-dialog"
|
||||||
tabs_container = dialog.find("nav", class_="settings-tabs")
|
tabs_container = dialog.find("nav", class_="settings-tabs")
|
||||||
assert tabs_container is not None, "Missing nav.settings-tabs inside #settings-dialog"
|
assert tabs_container is not None, (
|
||||||
|
"Missing nav.settings-tabs inside #settings-dialog"
|
||||||
|
)
|
||||||
expected_tabs = ["display", "sessions", "notifications", "new-session"]
|
expected_tabs = ["display", "sessions", "notifications", "new-session"]
|
||||||
for tab_value in expected_tabs:
|
for tab_value in expected_tabs:
|
||||||
tab = tabs_container.find("button", attrs={"data-tab": tab_value})
|
tab = tabs_container.find("button", attrs={"data-tab": tab_value})
|
||||||
assert tab is not None, f"Missing tab button with data-tab='{tab_value}' in settings-tabs"
|
assert tab is not None, (
|
||||||
|
f"Missing tab button with data-tab='{tab_value}' in settings-tabs"
|
||||||
|
)
|
||||||
# Display tab must be active by default
|
# Display tab must be active by default
|
||||||
display_tab = tabs_container.find("button", attrs={"data-tab": "display"})
|
display_tab = tabs_container.find("button", attrs={"data-tab": "display"})
|
||||||
assert display_tab is not None
|
assert display_tab is not None
|
||||||
@@ -446,7 +468,9 @@ def test_html_settings_display_panel_controls() -> None:
|
|||||||
for ctrl_id in ("setting-font-size", "setting-hover-delay", "setting-grid-columns"):
|
for ctrl_id in ("setting-font-size", "setting-hover-delay", "setting-grid-columns"):
|
||||||
el = dialog.find(id=ctrl_id)
|
el = dialog.find(id=ctrl_id)
|
||||||
assert el is not None, f"Missing #{ctrl_id} inside #settings-dialog"
|
assert el is not None, f"Missing #{ctrl_id} inside #settings-dialog"
|
||||||
assert el.name == "select", f"#{ctrl_id} must be a <select> element, got: {el.name}"
|
assert el.name == "select", (
|
||||||
|
f"#{ctrl_id} must be a <select> element, got: {el.name}"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_html_settings_font_size_options() -> None:
|
def test_html_settings_font_size_options() -> None:
|
||||||
@@ -498,3 +522,58 @@ def test_html_settings_grid_columns_options() -> None:
|
|||||||
assert selected_opt.get("value") == "auto", (
|
assert selected_opt.get("value") == "auto", (
|
||||||
f"setting-grid-columns default selection must be auto, got: {selected_opt.get('value')}"
|
f"setting-grid-columns default selection must be auto, got: {selected_opt.get('value')}"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_html_settings_panels_use_data_tab() -> None:
|
||||||
|
"""settings-panel elements must use data-tab (not data-panel) to match switchSettingsTab().
|
||||||
|
|
||||||
|
switchSettingsTab() in app.js reads panel.dataset.tab which corresponds to the
|
||||||
|
data-tab HTML attribute. If panels use data-panel instead, all panels get hidden
|
||||||
|
on the first tab click — the entire settings dialog becomes non-functional.
|
||||||
|
"""
|
||||||
|
soup = _SOUP
|
||||||
|
dialog = soup.find(id="settings-dialog")
|
||||||
|
assert dialog is not None, "Missing #settings-dialog"
|
||||||
|
panels = dialog.find_all(class_="settings-panel")
|
||||||
|
assert len(panels) == 4, (
|
||||||
|
f"Expected 4 .settings-panel elements, found: {len(panels)}"
|
||||||
|
)
|
||||||
|
for panel in panels:
|
||||||
|
assert panel.get("data-tab") is not None, (
|
||||||
|
f"settings-panel must use data-tab attribute, found: {dict(panel.attrs)}"
|
||||||
|
)
|
||||||
|
assert panel.get("data-panel") is None, (
|
||||||
|
f"settings-panel must NOT use data-panel attribute (use data-tab instead), found: {dict(panel.attrs)}"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_html_settings_tab_panel_data_tab_alignment() -> None:
|
||||||
|
"""Every tab button data-tab value must have a matching settings-panel data-tab value.
|
||||||
|
|
||||||
|
Cross-check test: verifies the HTML attribute names are consistent between tab buttons
|
||||||
|
(which use data-tab) and content panels (which must also use data-tab). A mismatch
|
||||||
|
causes switchSettingsTab() to read undefined for every panel, hiding all panels.
|
||||||
|
"""
|
||||||
|
soup = _SOUP
|
||||||
|
dialog = soup.find(id="settings-dialog")
|
||||||
|
assert dialog is not None, "Missing #settings-dialog"
|
||||||
|
|
||||||
|
# Collect tab button data-tab values
|
||||||
|
tabs_container = dialog.find("nav", class_="settings-tabs")
|
||||||
|
assert tabs_container is not None, "Missing nav.settings-tabs"
|
||||||
|
tab_buttons = tabs_container.find_all("button", attrs={"data-tab": True})
|
||||||
|
tab_values = {str(btn.get("data-tab")) for btn in tab_buttons}
|
||||||
|
assert len(tab_values) > 0, "No tab buttons with data-tab found"
|
||||||
|
|
||||||
|
# Collect panel data-tab values
|
||||||
|
panels = dialog.find_all(class_="settings-panel")
|
||||||
|
panel_values = {
|
||||||
|
str(p.get("data-tab")) for p in panels if p.get("data-tab") is not None
|
||||||
|
}
|
||||||
|
|
||||||
|
# Every tab button must have a matching panel
|
||||||
|
missing_panels = tab_values - panel_values
|
||||||
|
assert not missing_panels, (
|
||||||
|
f"Tab buttons {missing_panels} have no matching settings-panel[data-tab=...]. "
|
||||||
|
f"Panel data-tab values found: {panel_values}"
|
||||||
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user