Files
hermes-webui/tests/test_sienna_skin.py
T
nesquena-hermes 3f838fc31a release: v0.50.244 (#1308)
release: v0.50.244

Batch release of 4 PRs:

- #1303 (@fecolinhares) — TTS playback of agent responses via Web Speech API.
  Per-message speaker button + auto-read toggle + voice/rate/pitch in
  Settings. localStorage-only state. Closes #499.

- #1304 — Stale saved session 404 cleanup + structured api() errors.
  Salvaged from #1084. Independently approved on 358275e.

- #1306 — Cmd/Ctrl+K works while a conversation is busy.
  Salvaged from #1084. Independently approved on 2e8a239.

- #1307 — Sienna skin (warm clay & sand earth palette).
  Salvaged from #1084. Independently approved on 5cd79c8.

Tests: 3290 passed, 2 skipped, 3 xpassed, 0 failures (was 3254; +36 tests).

Independently reviewed and approved by nesquena (commit 47f0e0d). End-to-end
trace verified the TTS flow; security audit confirmed SpeechSynthesisUtterance
is plain-text-only with no XSS surface; behavioural harness confirmed
_stripForTTS handles all 12 markdown-stripping cases; bounds clamping on
rate/pitch verified; opt-in behavior verified.
2026-04-29 21:34:27 -07:00

80 lines
3.4 KiB
Python

"""Sienna skin: warm clay/sand earth palette, opt-in via Settings → Skin."""
from pathlib import Path
REPO = Path(__file__).parent.parent
CSS = (REPO / "static" / "style.css").read_text(encoding="utf-8")
BOOT_JS = (REPO / "static" / "boot.js").read_text(encoding="utf-8")
INDEX_HTML = (REPO / "static" / "index.html").read_text(encoding="utf-8")
def test_sienna_skin_present_in_skins_list():
"""The Sienna skin must be exposed in the picker grid via _SKINS."""
assert "{name:'Sienna'" in BOOT_JS, "Sienna skin missing from _SKINS list"
assert "'#D97757','#C06A49','#9A523A'" in BOOT_JS, (
"Sienna preview swatches missing"
)
def test_sienna_skin_in_early_init_allowlist():
"""The early-init skin allowlist must accept 'sienna'."""
assert "sienna:1" in INDEX_HTML, (
"Sienna missing from early-init skin allowlist; saved skin would be "
"rejected and reset to default on boot"
)
def test_sienna_skin_palette_has_full_light_and_dark():
"""Sienna defines both light and dark scoped palettes."""
assert ':root[data-skin="sienna"]{' in CSS, (
"Sienna light-mode palette block missing"
)
assert ':root.dark[data-skin="sienna"]{' in CSS, (
"Sienna dark-mode palette block missing"
)
# Spot-check that the palette is a full rewrite (not just --accent)
for token in ("--bg:#FAF9F5", "--sidebar:#F0EEE6", "--accent:#D97757"):
assert token in CSS, f"Sienna light palette token missing: {token}"
for token in ("--bg:#1F1E1C", "--sidebar:#262522", "--accent:#E0896D"):
assert token in CSS, f"Sienna dark palette token missing: {token}"
def test_sienna_skin_does_not_force_migration():
"""Sienna must not be silently migrated onto existing users.
The early-init script in index.html must NOT contain logic that flips an
existing 'default' skin to 'sienna' on first load. New users keep the gold
default; users opt in via Settings → Skin.
"""
# The skin allowlist line should NOT contain a sienna-migration flag.
init_script_idx = INDEX_HTML.find("var themes=")
end_idx = INDEX_HTML.find("</script>", init_script_idx)
init_block = INDEX_HTML[init_script_idx:end_idx]
forbidden = ["sienna-migrated", "skin-sienna-migrated", "skin='sienna'", 'skin="sienna"']
for marker in forbidden:
assert marker not in init_block, (
f"Sienna skin must be opt-in, not force-migrated. Found '{marker}' "
f"in early-init script."
)
def test_default_theme_is_still_dark():
"""Adding a new skin must not change the default theme."""
# The early-init script defaults to 'dark' when no saved theme exists.
init_script_idx = INDEX_HTML.find("var themes=")
end_idx = INDEX_HTML.find("</script>", init_script_idx)
init_block = INDEX_HTML[init_script_idx:end_idx]
assert "||'dark'" in init_block, (
"Default theme must remain 'dark' (the existing baseline)"
)
def test_sienna_new_chat_button_specificity_guards_against_clay_on_clay():
"""The new-chat button needs higher specificity than the base
:root:not(.dark) .new-chat-btn rule, otherwise the inherited
color:var(--accent-text) collides with the solid-accent background and
produces invisible clay-on-clay text in light mode."""
assert ':root[data-skin="sienna"]:not(.dark) .new-chat-btn' in CSS, (
"Sienna light-mode .new-chat-btn override missing — clay-on-clay risk"
)