""" Tests for issue #673 — sidebar density mode for the session list. Covers: - api/config.py: sidebar_density registered in defaults + enum validation - static/index.html: settingsSidebarDensity field and i18n wiring present - static/boot.js: boot path applies window._sidebarDensity with compact default - static/panels.js: load/save settings wire sidebar_density correctly - static/sessions.js: detailed mode renders message count + model, and profile only when the "show all profiles" toggle is active - static/i18n.js: locale keys exist for all shipped locales - Integration: GET/POST /api/settings round-trip sidebar_density """ import json import pathlib import re import unittest import urllib.error import urllib.request REPO_ROOT = pathlib.Path(__file__).parent.parent CONFIG_PY = (REPO_ROOT / "api" / "config.py").read_text(encoding="utf-8") INDEX_HTML = (REPO_ROOT / "static" / "index.html").read_text(encoding="utf-8") BOOT_JS = (REPO_ROOT / "static" / "boot.js").read_text(encoding="utf-8") PANELS_JS = (REPO_ROOT / "static" / "panels.js").read_text(encoding="utf-8") SESSIONS_JS = (REPO_ROOT / "static" / "sessions.js").read_text(encoding="utf-8") STYLE_CSS = (REPO_ROOT / "static" / "style.css").read_text(encoding="utf-8") I18N_JS = (REPO_ROOT / "static" / "i18n.js").read_text(encoding="utf-8") from tests._pytest_port import BASE def _get(path): with urllib.request.urlopen(BASE + path, timeout=10) as r: return json.loads(r.read()), r.status def _post(path, body=None): data = json.dumps(body or {}).encode() req = urllib.request.Request( BASE + path, data=data, headers={"Content-Type": "application/json"} ) try: with urllib.request.urlopen(req, timeout=10) as r: return json.loads(r.read()), r.status except urllib.error.HTTPError as e: return json.loads(e.read()), e.code class TestSidebarDensityConfig(unittest.TestCase): def test_sidebar_density_in_defaults(self): self.assertIn('"sidebar_density"', CONFIG_PY) def test_sidebar_density_default_is_compact(self): self.assertRegex(CONFIG_PY, r'"sidebar_density"\s*:\s*"compact"') def test_sidebar_density_in_enum_values(self): self.assertIn('"sidebar_density": {"compact", "detailed"}', CONFIG_PY) class TestSidebarDensityHTML(unittest.TestCase): def test_settings_select_present(self): self.assertIn('id="settingsSidebarDensity"', INDEX_HTML) def test_i18n_wiring_present(self): for key in ( 'data-i18n="settings_label_sidebar_density"', 'data-i18n="settings_desc_sidebar_density"', 'data-i18n="settings_sidebar_density_compact"', 'data-i18n="settings_sidebar_density_detailed"', ): self.assertIn(key, INDEX_HTML) class TestSidebarDensityBootAndPanels(unittest.TestCase): def test_boot_applies_sidebar_density(self): self.assertIn( "window._sidebarDensity=(s.sidebar_density==='detailed'?'detailed':'compact');", BOOT_JS, ) def test_boot_fallback_is_compact(self): self.assertIn("window._sidebarDensity='compact';", BOOT_JS) def test_settings_panel_reads_sidebar_density(self): self.assertIn("settingsSidebarDensity", PANELS_JS) self.assertIn( "settings.sidebar_density==='detailed'?'detailed':'compact'", PANELS_JS, ) def test_settings_save_writes_sidebar_density(self): self.assertIn("body.sidebar_density=sidebarDensity;", PANELS_JS) self.assertIn( "window._sidebarDensity=sidebarDensity==='detailed'?'detailed':'compact';", PANELS_JS, ) class TestSidebarDensitySessionRendering(unittest.TestCase): def test_detailed_mode_branch_present(self): self.assertIn( "const density=(window._sidebarDensity==='detailed'?'detailed':'compact');", SESSIONS_JS, ) self.assertIn("if(density==='detailed')", SESSIONS_JS) def test_detailed_mode_uses_message_count_and_model(self): self.assertIn("typeof s.message_count==='number'?s.message_count:0", SESSIONS_JS) self.assertIn("if(s.model) metaBits.push(s.model);", SESSIONS_JS) self.assertIn("t('session_meta_messages', msgCount)", SESSIONS_JS) def test_profile_only_when_show_all_profiles(self): self.assertIn( "if(_showAllProfiles&&s.profile) metaBits.push(s.profile);", SESSIONS_JS ) def test_session_meta_css_hook_present(self): self.assertIn(".session-meta", STYLE_CSS) class TestSidebarDensityI18N(unittest.TestCase): def _extract_locale_block(self, start_marker, end_marker): start = I18N_JS.find(start_marker) end = I18N_JS.find(end_marker, start) self.assertGreater(start, -1) self.assertGreater(end, start) return I18N_JS[start:end] def test_all_locale_blocks_have_sidebar_density_keys(self): locale_ranges = [ ("\n en: {", "\n ru: {"), ("\n ru: {", "\n es: {"), ("\n es: {", "\n de: {"), ("\n de: {", "\n zh: {"), ("\n zh: {", "\n // Traditional Chinese (zh-Hant)"), ("\n // Traditional Chinese (zh-Hant)\n 'zh-Hant': {", "\n};"), ] required = ( "settings_label_sidebar_density", "settings_desc_sidebar_density", "settings_sidebar_density_compact", "settings_sidebar_density_detailed", "session_meta_messages", ) for start, end in locale_ranges: block = self._extract_locale_block(start, end) for key in required: self.assertIn(key, block, f"{key} missing from locale block {start}") class TestSidebarDensitySettingsAPI(unittest.TestCase): def test_sidebar_density_default_is_compact(self): try: data, status = _get("/api/settings") except OSError: self.skipTest("Server not running on test server port") self.assertEqual(status, 200) self.assertEqual(data.get("sidebar_density"), "compact") def test_sidebar_density_round_trips_detailed(self): try: _, status = _post("/api/settings", {"sidebar_density": "detailed"}) except OSError: self.skipTest("Server not running on test server port") self.assertEqual(status, 200) data, _ = _get("/api/settings") self.assertEqual(data.get("sidebar_density"), "detailed") _post("/api/settings", {"sidebar_density": "compact"}) def test_invalid_sidebar_density_is_ignored(self): try: _post("/api/settings", {"sidebar_density": "compact"}) data, status = _post("/api/settings", {"sidebar_density": "nope"}) except OSError: self.skipTest("Server not running on test server port") self.assertEqual(status, 200) self.assertEqual(data.get("sidebar_density"), "compact") current, _ = _get("/api/settings") self.assertEqual(current.get("sidebar_density"), "compact")