Files
hermes-webui/tests/test_issue3595_activity_default_expanded.py
T
nesquena-hermes 6951d10ae5 rebase #4411 onto master (v0.51.542) for deep-review — Stable Assistant Turn Anchors capstone
franksong2702's activity_scene_v1 anchor-scene model: live-stream/settled/refresh/re-entry
show the same ordered turn. Rebased clean (no conflicts) onto current master.
Co-authored-by: franksong2702 <franksong2702@users.noreply.github.com>
2026-06-20 21:48:55 +00:00

177 lines
8.5 KiB
Python

"""Pin the full behavioral contract for the Worklog expanded-default setting."""
import json
import pathlib
import re
ROOT = pathlib.Path(__file__).resolve().parent.parent
def _function_body(src, name):
marker = f"function {name}"
start = src.index(marker)
brace = src.index("{", start)
depth = 0
for idx in range(brace, len(src)):
ch = src[idx]
if ch == "{":
depth += 1
elif ch == "}":
depth -= 1
if depth == 0:
return src[brace + 1:idx]
raise AssertionError(f"function {name} body not found")
def test_setting_in_defaults():
src = (ROOT / "api" / "config.py").read_text(encoding="utf-8")
assert '"worklog_details_expanded_default"' in src or "'worklog_details_expanded_default'" in src, \
"worklog_details_expanded_default must exist in _SETTINGS_DEFAULTS"
# Verify default is False
assert re.search(r'["\']worklog_details_expanded_default["\']:\s*False', src), \
"worklog_details_expanded_default default must be False (collapsed)"
def test_setting_in_bool_keys():
src = (ROOT / "api" / "config.py").read_text(encoding="utf-8")
assert re.search(r'_SETTINGS_BOOL_KEYS\b.*?worklog_details_expanded_default', src, re.DOTALL), \
"worklog_details_expanded_default must appear inside _SETTINGS_BOOL_KEYS (not just anywhere in config.py)"
def test_legacy_activity_feed_setting_migrates_without_remaining_primary_semantics():
src = (ROOT / "api" / "config.py").read_text(encoding="utf-8")
assert '"activity_feed_expanded_default"' in src, \
"config.py should still accept the legacy key as a migration alias"
assert re.search(r'_SETTINGS_LEGACY_DROP_KEYS\b.*?activity_feed_expanded_default', src, re.DOTALL), \
"The legacy Activity Feed key should be dropped from primary settings after migration"
assert 'settings["worklog_details_expanded_default"] = bool(' in src, \
"load_settings should migrate legacy Activity Feed values into the Worklog details key"
assert 'settings.pop("activity_feed_expanded_default", None)' in src, \
"save_settings should not persist the legacy Activity Feed key"
def test_legacy_activity_feed_setting_migrates_on_load_and_save(monkeypatch, tmp_path):
from api import config
settings_file = tmp_path / "settings.json"
settings_file.write_text(
json.dumps({"activity_feed_expanded_default": True}),
encoding="utf-8",
)
monkeypatch.setattr(config, "SETTINGS_FILE", settings_file)
loaded = config.load_settings()
assert loaded["worklog_details_expanded_default"] is True
assert "activity_feed_expanded_default" not in loaded
saved = config.save_settings({"activity_feed_expanded_default": False})
assert saved["worklog_details_expanded_default"] is False
on_disk = json.loads(settings_file.read_text(encoding="utf-8"))
assert on_disk["worklog_details_expanded_default"] is False
assert "activity_feed_expanded_default" not in on_disk
def test_boot_initializes_window_flag():
src = (ROOT / "static" / "boot.js").read_text(encoding="utf-8")
assert "worklog_details_expanded_default" in src, \
"boot.js must initialize window._worklogDetailsExpandedByDefault from settings"
assert "s.activity_feed_expanded_default" in src, \
"boot.js should tolerate old servers that still return the legacy key"
def test_ensure_activity_group_checks_flag():
src = (ROOT / "static" / "ui.js").read_text(encoding="utf-8")
assert "_worklogDetailsExpandedByDefault" in src, \
"ensureActivityGroup must check window._worklogDetailsExpandedByDefault"
def test_setting_controls_worklog_item_details_not_only_outer_group():
src = (ROOT / "static" / "ui.js").read_text(encoding="utf-8")
assert "function _worklogDetailsExpandedDefault()" in src, \
"Worklog detail cards should share a single expanded-default helper"
thinking_fn = _function_body(src, "_thinkingCardHtml")
assert "_worklogDetailsExpandedDefault()" in thinking_fn, \
"Thinking cards should respect the Worklog details default"
legacy_thinking_fn = _function_body(src, "_thinkingMarkup")
assert "_worklogDetailsExpandedDefault()" in legacy_thinking_fn, \
"Thinking update fallback markup should not overwrite the Worklog details default"
assert "!isSimplifiedToolCalling()" not in legacy_thinking_fn, \
"The deprecated compact-tool toggle should not keep dead branches in Thinking markup"
tool_fn = _function_body(src, "buildToolCard")
assert "const openClass='';" in tool_fn and "hasDetail&&_worklogDetailsExpandedDefault()" not in tool_fn, \
"Tool cards must stay one-line by default; details should open only after the user expands that card"
grouped_tools_fn = _function_body(src, "_syncToolRowsContainer")
assert "const shouldOpen=wasOpen||_worklogDetailsExpandedDefault()" in grouped_tools_fn, \
"Multi-tool Worklog groups should respect the default without closing a group the user already opened"
def test_setting_toggle_applies_to_existing_worklog_details():
ui_src = (ROOT / "static" / "ui.js").read_text(encoding="utf-8")
helper = _function_body(ui_src, "_applyWorklogDetailsExpandedDefault")
assert "scope.querySelectorAll('.thinking-card')" in helper, \
"Toggling the setting should update existing Thinking cards"
assert "scope.querySelectorAll('.tool-card')" not in helper, \
"Toggling the setting must not expand every Tool card into multi-line rows"
assert "data-tool-worklog-tool-group" in helper and "aria-expanded" in helper, \
"Toggling the setting should update existing multi-tool Worklog groups"
panels_src = (ROOT / "static" / "panels.js").read_text(encoding="utf-8")
onchange_block = re.search(
r"worklogDetailsExpandedCb\.onchange=function\(\)\{(?P<body>.*?)\n\s*\};",
panels_src,
re.DOTALL,
)
assert onchange_block, "Worklog detail checkbox should have an onchange handler"
assert "_applyWorklogDetailsExpandedDefault()" in onchange_block.group("body"), \
"Changing the Worklog detail setting should apply the new default immediately"
def test_appearance_autosave_does_not_reapply_worklog_details_over_manual_state():
panels_src = (ROOT / "static" / "panels.js").read_text(encoding="utf-8")
autosave_fn = _function_body(panels_src, "_autosaveAppearanceSettings")
assert "_worklogDetailsExpandedByDefault" in autosave_fn, \
"Autosave should still reconcile the stored Worklog default flag"
assert "_applyWorklogDetailsExpandedDefault()" not in autosave_fn, \
"Autosave responses must not overwrite per-turn manual Worklog expand/collapse choices"
def test_per_turn_override():
src = (ROOT / "static" / "ui.js").read_text(encoding="utf-8")
assert re.search(r"savedState\s*===\s*['\"]closed['\"]", src), \
"ensureActivityGroup must check savedState==='closed' so per-turn persistence overrides the global default"
def test_collapse_class_applied():
src = (ROOT / "static" / "ui.js").read_text(encoding="utf-8")
assert "tool-call-group-collapsed" in src, \
"The collapsed class 'tool-call-group-collapsed' must be present in ui.js for the activity group"
def test_settings_checkbox_exists():
src = (ROOT / "static" / "index.html").read_text(encoding="utf-8")
assert "settings_label_worklog_details_expanded_default" in src, \
"index.html must have a settings checkbox with data-i18n for worklog_details_expanded_default"
assert "Open Worklog details automatically" in src, \
"The setting copy should describe Worklog details, not the old Activity Feed wording"
assert "Worklog details stay folded by default" in src, \
"The setting copy must make the off/default state folded"
def test_panels_wiring():
src = (ROOT / "static" / "panels.js").read_text(encoding="utf-8")
assert "worklog_details_expanded_default" in src, \
"panels.js must read/write the worklog_details_expanded_default setting"
assert "activity_feed_expanded_default: worklogDetailsExpanded" in src, \
"panels.js should include a legacy POST alias so old servers can persist the setting during rolling updates"
def test_i18n_keys():
src = (ROOT / "static" / "i18n.js").read_text(encoding="utf-8")
assert "settings_label_worklog_details_expanded_default" in src, \
"i18n.js must have the label key for the setting"
assert "settings_desc_worklog_details_expanded_default" in src, \
"i18n.js must have the description key for the setting"