Files
hermes-webui/tests/test_1059_settings_picker_active_state.py
T
nesquena-hermes 01404ac062 v0.50.211: compact timestamps, adaptive title refresh, settings picker fix (#1061)
* Shorten session sidebar relative time labels

* feat: adaptive session title refresh based on conversation evolution

Addresses #869 — the 'Optional' part: adapt session names to current
conversation context instead of only generating once from the first exchange.

Backend (api/streaming.py):
- Add _latest_exchange_snippets() to extract last user+assistant pair
- Add _count_exchanges() to count user messages
- Add _get_title_refresh_interval() to read the setting
- Add _run_background_title_refresh() — refreshes title from latest exchange
  with LLM, skips if title is unchanged or user manually renamed
- Add _maybe_schedule_title_refresh() — checks exchange count and schedules
  refresh after stream_end (non-blocking)

Config (api/config.py):
- Add auto_title_refresh_every setting (default '0' = off)
- Enum validation: {'0', '5', '10', '20'}

Frontend:
- Settings UI dropdown (static/index.html)
- Wire up load/save in panels.js
- i18n keys for all 6 locales (en/ru/es/de/zh/zh-Hant)

Default: off. Opt-in via Settings > Conversation > Adaptive title refresh.

* test: add 37 tests for adaptive title refresh helpers

Covers all five new functions introduced in this PR:
  _count_exchanges, _latest_exchange_snippets, _get_title_refresh_interval,
  _run_background_title_refresh, _maybe_schedule_title_refresh

Co-authored-by: bergeouss <bergeouss@users.noreply.github.com>

* fix(settings): show selected state on theme/skin/font-size picker cards

The CSS rule `#mainSettings .theme-pick-btn { border-color: var(--border) !important }` was
overriding the inline `style.borderColor = "var(--accent)"` set by `_syncThemePicker()` and
siblings — `!important` beats inline styles. Active cards showed no visual highlight.

Fix: move to `.active` CSS class with `border-color:var(--accent)!important` so the active
rule wins over the base rule, and clear the stale inline borderColor/boxShadow from the
sync functions. 5 regression tests added.

Closes #1057

* fix: rename test file to match PR number, fix stale issue reference

* docs: v0.50.211 release notes and version bump

Compact sidebar timestamps, adaptive title refresh (opt-in), settings picker fix.

* docs(changelog): correct settings tab for adaptive title refresh

The v0.50.211 entry for #1058 said "Settings → Appearance" but the
toggle is actually rendered inside settingsPanePreferences (the
Preferences tab) per static/index.html:604+. The commit message also
had the wrong tab ("Conversation"). Updated CHANGELOG to match the
actual UI surface so users can find the toggle.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix: create state dir before writing settings file

save_settings() called SETTINGS_FILE.write_text() without ensuring the
parent directory exists. In fresh environments (CI, first run without
HERMES_WEBUI_STATE_DIR set) this raised FileNotFoundError.
Add mkdir(parents=True, exist_ok=True) before the write.

---------

Co-authored-by: Pavol Biely <biely@webtec.sk>
Co-authored-by: bergeouss <bergeouss@users.noreply.github.com>
Co-authored-by: nesquena-hermes <nesquena-hermes@users.noreply.github.com>
Co-authored-by: Nathan Esquenazi <nesquena@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 17:50:58 -07:00

85 lines
4.0 KiB
Python

"""Regression tests for settings picker active-state highlighting.
The theme, skin, and font-size pickers in the Appearance settings tab must show
the currently-selected option with a visible accent border. This was broken because
the CSS rule used !important on border-color:var(--border) which overrode the inline
style that _syncThemePicker/etc. set. Fixed by moving to .active CSS class + !important
override on the active state.
Issue: #1059 (settings picker active state)
"""
from pathlib import Path
BOOT_JS = (Path(__file__).parent.parent / "static" / "boot.js").read_text(encoding="utf-8")
STYLE_CSS = (Path(__file__).parent.parent / "static" / "style.css").read_text(encoding="utf-8")
class TestSettingsPickerActiveState:
"""The selected picker card must be visually distinct via the .active class."""
def test_theme_picker_uses_active_class(self):
"""_syncThemePicker must toggle .active class, not set inline borderColor."""
idx = BOOT_JS.find("function _syncThemePicker(")
assert idx >= 0, "_syncThemePicker function not found in boot.js"
body = BOOT_JS[idx:idx + 300]
assert "classList.toggle" in body, (
"_syncThemePicker must use classList.toggle('active', ...) — "
"inline style.borderColor is overridden by !important CSS rules"
)
# Confirm no accent/border2 color values set inline (clearing with '' is OK)
assert "var(--accent)" not in body and "var(--border2)" not in body, (
"_syncThemePicker must not set var(--accent) or var(--border2) inline — "
"those are overridden by !important CSS rules"
)
def test_font_size_picker_uses_active_class(self):
"""_syncFontSizePicker must toggle .active class."""
idx = BOOT_JS.find("function _syncFontSizePicker(")
assert idx >= 0, "_syncFontSizePicker function not found in boot.js"
body = BOOT_JS[idx:idx + 300]
assert "classList.toggle" in body, (
"_syncFontSizePicker must use classList.toggle('active', ...)"
)
assert "var(--accent)" not in body and "var(--border2)" not in body, (
"_syncFontSizePicker must not set var(--accent) or var(--border2) inline"
)
def test_skin_picker_uses_active_class(self):
"""_syncSkinPicker must toggle .active class."""
idx = BOOT_JS.find("function _syncSkinPicker(")
assert idx >= 0, "_syncSkinPicker function not found in boot.js"
body = BOOT_JS[idx:idx + 300]
assert "classList.toggle" in body, (
"_syncSkinPicker must use classList.toggle('active', ...)"
)
assert "var(--accent)" not in body and "var(--border2)" not in body, (
"_syncSkinPicker must not set var(--accent) or var(--border2) inline"
)
def test_css_active_rule_beats_base_rule(self):
"""CSS must have a .active rule with !important that overrides the base border-color rule."""
assert ".theme-pick-btn.active" in STYLE_CSS, (
"style.css must have a .theme-pick-btn.active rule"
)
assert ".font-size-pick-btn.active" in STYLE_CSS, (
"style.css must have a .font-size-pick-btn.active rule"
)
assert ".skin-pick-btn.active" in STYLE_CSS, (
"style.css must have a .skin-pick-btn.active rule"
)
# The active rule must use !important to beat the base !important rule
idx = STYLE_CSS.find(".theme-pick-btn.active")
rule = STYLE_CSS[idx:idx + 200]
assert "!important" in rule, (
".theme-pick-btn.active must use !important to override "
"the base border-color:var(--border)!important rule"
)
def test_active_rule_uses_accent_color(self):
"""The .active rule must apply the accent color to make selection visible."""
idx = STYLE_CSS.find(".theme-pick-btn.active")
rule = STYLE_CSS[idx:idx + 200]
assert "var(--accent)" in rule, (
".theme-pick-btn.active must set border-color to var(--accent)"
)