mirror of
https://github.com/nesquena/hermes-webui.git
synced 2026-05-24 18:50:15 +00:00
4fea813adc
Container restart / in-place upgrade left the previous service worker still controlling open tabs. Its fetch handler intercepted 'static/style.css', matched the unversioned URL exactly against its old shell cache, and returned the OLD CSS — while the JS files (which already carry ?v=__WEBUI_VERSION__) hit the cache as misses and loaded fresh from network. New JS + old CSS broke the layout until a force refresh bypassed the SW. Fix is a 1-line attribute change plus aligning the SW pre-cache list: * static/index.html: add ?v=__WEBUI_VERSION__ to the style.css link, matching the pattern already in use for every JS file in the page. * static/sw.js: add the same ?v=__CACHE_VERSION__ suffix to every versioned entry in SHELL_ASSETS so that pre-cache URLs match what the page actually requests. Unversioned entries (root, manifest, favicons) stay unversioned. Tests: * New regression test_index_versions_stylesheet (lock the href) and test_sw_shell_assets_match_versioned_asset_urls in test_pwa_manifest_sw.py. * test_workspace_panel_preload_marker_restored_in_head in test_sprint37.py loosened to match the css link prefix (preserves the ordering invariant). Verified live on port 8789: served HTML carries 'static/style.css?v=v0.50.275-dirty' and SW SHELL_ASSETS receive the matching VQ at request time. Closes #1507.
116 lines
5.9 KiB
Python
116 lines
5.9 KiB
Python
"""
|
|
Sprint 37 Tests: Workspace panel open/closed state persists across refreshes via localStorage.
|
|
"""
|
|
import pathlib
|
|
import re
|
|
|
|
REPO_ROOT = pathlib.Path(__file__).parent.parent
|
|
BOOT_JS = (REPO_ROOT / "static" / "boot.js").read_text()
|
|
HTML = (REPO_ROOT / "static" / "index.html").read_text()
|
|
|
|
|
|
# ── Persistence: save on change ───────────────────────────────────────────────
|
|
|
|
def test_workspace_panel_saves_to_localstorage():
|
|
"""_setWorkspacePanelMode must call localStorage.setItem with hermes-webui-workspace-panel."""
|
|
assert "hermes-webui-workspace-panel" in BOOT_JS, \
|
|
"boot.js must use localStorage key 'hermes-webui-workspace-panel' to persist panel state"
|
|
|
|
|
|
def test_workspace_panel_save_inside_set_mode():
|
|
"""localStorage.setItem for panel state must live inside _setWorkspacePanelMode."""
|
|
fn_idx = BOOT_JS.find("function _setWorkspacePanelMode(")
|
|
fn_end = BOOT_JS.find("\n}", fn_idx) + 2
|
|
fn_body = BOOT_JS[fn_idx:fn_end]
|
|
assert "hermes-webui-workspace-panel" in fn_body, \
|
|
"localStorage save must be inside _setWorkspacePanelMode so every state change is captured"
|
|
|
|
|
|
def test_workspace_panel_saves_open_value():
|
|
"""When the panel is open, localStorage must be set to 'open'."""
|
|
fn_idx = BOOT_JS.find("function _setWorkspacePanelMode(")
|
|
fn_end = BOOT_JS.find("\n}", fn_idx) + 2
|
|
fn_body = BOOT_JS[fn_idx:fn_end]
|
|
assert "'open'" in fn_body or '"open"' in fn_body, \
|
|
"_setWorkspacePanelMode must store 'open' for an open panel state"
|
|
|
|
|
|
def test_workspace_panel_saves_closed_value():
|
|
"""When the panel is closed, localStorage must be set to 'closed'."""
|
|
fn_idx = BOOT_JS.find("function _setWorkspacePanelMode(")
|
|
fn_end = BOOT_JS.find("\n}", fn_idx) + 2
|
|
fn_body = BOOT_JS[fn_idx:fn_end]
|
|
assert "'closed'" in fn_body or '"closed"' in fn_body, \
|
|
"_setWorkspacePanelMode must store 'closed' for a closed panel state"
|
|
|
|
|
|
# ── Persistence: restore on boot ─────────────────────────────────────────────
|
|
|
|
def test_workspace_panel_restored_on_boot():
|
|
"""Boot IIFE must read hermes-webui-workspace-panel from localStorage and restore the mode."""
|
|
# Find the boot IIFE (the async IIFE at the bottom of boot.js)
|
|
iife_idx = BOOT_JS.rfind("(async function")
|
|
if iife_idx < 0:
|
|
iife_idx = BOOT_JS.rfind("(async()=>{")
|
|
iife_body = BOOT_JS[iife_idx:]
|
|
assert "hermes-webui-workspace-panel" in iife_body, \
|
|
"Boot IIFE must read 'hermes-webui-workspace-panel' from localStorage to restore panel state on load"
|
|
|
|
|
|
def test_workspace_panel_restore_sets_browse_mode():
|
|
"""When localStorage says 'open', boot must set _workspacePanelMode to 'browse' before syncing."""
|
|
iife_idx = BOOT_JS.rfind("(async function")
|
|
if iife_idx < 0:
|
|
iife_idx = BOOT_JS.rfind("(async()=>{")
|
|
iife_body = BOOT_JS[iife_idx:]
|
|
# The restore block must assign _workspacePanelMode = 'browse'
|
|
assert "_workspacePanelMode='browse'" in iife_body or "_workspacePanelMode = 'browse'" in iife_body, \
|
|
"Boot must set _workspacePanelMode='browse' when restoring an open panel"
|
|
|
|
|
|
def test_workspace_panel_restore_before_sync():
|
|
"""Restore must happen before syncWorkspacePanelState() so the state drives the initial render.
|
|
|
|
The boot IIFE has two paths: one for sessions with messages (restores panel pref before sync)
|
|
and one for empty/missing sessions (no panel to restore; syncs immediately). The test verifies
|
|
that in the normal session-restore path, the panel pref read precedes syncWorkspacePanelState().
|
|
We find the panelPref read (localStorage...workspace-panel-pref) and the LAST
|
|
syncWorkspacePanelState() call — the final one is always in the normal restore path.
|
|
"""
|
|
iife_idx = BOOT_JS.rfind("(async function")
|
|
if iife_idx < 0:
|
|
iife_idx = BOOT_JS.rfind("(async()=>{")
|
|
iife_body = BOOT_JS[iife_idx:]
|
|
# workspace-panel-pref is only read in the normal (has-messages) restore path
|
|
restore_pos = iife_body.find("hermes-webui-workspace-panel-pref")
|
|
# Use the LAST syncWorkspacePanelState() — this is the one in the normal restore path
|
|
sync_pos = iife_body.rfind("syncWorkspacePanelState()")
|
|
assert restore_pos >= 0, "restore read must be present in boot IIFE"
|
|
assert sync_pos >= 0, "syncWorkspacePanelState call must be present in boot IIFE"
|
|
assert restore_pos < sync_pos, \
|
|
"Workspace panel restore must happen BEFORE syncWorkspacePanelState() so the correct mode is applied"
|
|
|
|
|
|
def test_workspace_panel_preload_marker_restored_in_head():
|
|
"""index.html must preload the workspace panel state before the main stylesheet paints."""
|
|
marker = "document.documentElement.dataset.workspacePanel"
|
|
# The stylesheet href carries `?v=__WEBUI_VERSION__` for SW cache busting (#1507).
|
|
# We just need the link's start position; the version query doesn't change the
|
|
# ordering invariant this test enforces.
|
|
css_link = '<link rel="stylesheet" href="static/style.css'
|
|
marker_pos = HTML.find(marker)
|
|
css_pos = HTML.find(css_link)
|
|
assert marker_pos >= 0, "index.html must preload documentElement.dataset.workspacePanel from localStorage"
|
|
assert css_pos >= 0, "main stylesheet link missing from index.html"
|
|
assert marker_pos < css_pos, \
|
|
"workspace panel preload marker must be set before style.css loads to avoid first-paint flash"
|
|
|
|
|
|
def test_workspace_panel_mode_syncs_document_dataset():
|
|
"""_setWorkspacePanelMode must update documentElement.dataset.workspacePanel for runtime parity."""
|
|
fn_idx = BOOT_JS.find("function _setWorkspacePanelMode(")
|
|
fn_end = BOOT_JS.find("\n}", fn_idx) + 2
|
|
fn_body = BOOT_JS[fn_idx:fn_end]
|
|
assert "document.documentElement.dataset.workspacePanel" in fn_body, \
|
|
"_setWorkspacePanelMode must keep documentElement.dataset.workspacePanel in sync with the panel state"
|