mirror of
https://github.com/nesquena/hermes-webui.git
synced 2026-05-26 03:30:36 +00:00
125 lines
5.3 KiB
Python
125 lines
5.3 KiB
Python
"""
|
|
Tests for issue #1045 — bfcache layout broken on tab restore.
|
|
|
|
When the browser restores a page from bfcache (event.persisted === true),
|
|
the async boot IIFE does not re-run. The existing pageshow handler (added for
|
|
#822) only cleared the session search field and re-rendered the session list.
|
|
This left the rail, topbar, workspace panel, and resize handles in the stale
|
|
bfcache DOM state, producing a broken layout.
|
|
|
|
Fix: extend the pageshow handler to also call syncTopbar, syncWorkspacePanelState,
|
|
_initResizePanels, and startGatewaySSE — all guarded so missing helpers degrade.
|
|
"""
|
|
|
|
from pathlib import Path
|
|
|
|
ROOT = Path(__file__).parent.parent
|
|
|
|
|
|
def _boot_js() -> str:
|
|
return (ROOT / "static" / "boot.js").read_text(encoding="utf-8")
|
|
|
|
|
|
def _pageshow_handler(src: str) -> str:
|
|
marker = "window.addEventListener('pageshow'"
|
|
ps_idx = src.find(marker)
|
|
assert ps_idx != -1, "pageshow listener not found in boot.js"
|
|
brace = src.find("=> {", ps_idx)
|
|
assert brace != -1, "pageshow listener body not found"
|
|
brace = src.find("{", brace)
|
|
depth = 1
|
|
i = brace + 1
|
|
while i < len(src) and depth:
|
|
if src[i] == "{":
|
|
depth += 1
|
|
elif src[i] == "}":
|
|
depth -= 1
|
|
i += 1
|
|
assert depth == 0, "pageshow listener body did not close"
|
|
return src[brace + 1 : i - 1]
|
|
|
|
|
|
class TestBfcacheLayoutRestore:
|
|
def test_pageshow_calls_sync_topbar(self):
|
|
"""pageshow handler must call syncTopbar() on bfcache restore."""
|
|
src = _boot_js()
|
|
handler_body = _pageshow_handler(src)
|
|
assert "syncTopbar" in handler_body, (
|
|
"pageshow handler must call syncTopbar() to restore topbar state after bfcache"
|
|
)
|
|
|
|
def test_pageshow_calls_sync_workspace_panel_state(self):
|
|
"""pageshow handler must call syncWorkspacePanelState()."""
|
|
src = _boot_js()
|
|
handler_body = _pageshow_handler(src)
|
|
assert "syncWorkspacePanelState" in handler_body, (
|
|
"pageshow handler must call syncWorkspacePanelState() on bfcache restore"
|
|
)
|
|
|
|
|
|
def test_pageshow_calls_start_gateway_sse(self):
|
|
"""pageshow handler must call startGatewaySSE() to reconnect the dead SSE connection."""
|
|
src = _boot_js()
|
|
handler_body = _pageshow_handler(src)
|
|
assert "startGatewaySSE" in handler_body, (
|
|
"pageshow handler must restart gateway SSE (bfcache-persisted connections are dead)"
|
|
)
|
|
|
|
def test_pageshow_still_clears_session_search(self):
|
|
"""pageshow handler must still clear #sessionSearch (original #822 fix preserved)."""
|
|
src = _boot_js()
|
|
handler_body = _pageshow_handler(src)
|
|
assert "sessionSearch" in handler_body, (
|
|
"pageshow handler must still clear #sessionSearch (regression: #822 fix must be preserved)"
|
|
)
|
|
|
|
def test_pageshow_still_calls_render_session_list_from_cache(self):
|
|
"""pageshow handler must still call renderSessionListFromCache()."""
|
|
src = _boot_js()
|
|
handler_body = _pageshow_handler(src)
|
|
assert "renderSessionListFromCache" in handler_body, (
|
|
"pageshow handler must still call renderSessionListFromCache() (regression: #822 fix)"
|
|
)
|
|
|
|
def test_pageshow_does_not_call_init_resize_panels(self):
|
|
"""pageshow handler must NOT call _initResizePanels() — bfcache
|
|
preserves event listeners so re-attaching them stacks duplicates."""
|
|
src = _boot_js()
|
|
handler_body = _pageshow_handler(src)
|
|
assert "_initResizePanels" not in handler_body, (
|
|
"pageshow handler must not call _initResizePanels() — it stacks "
|
|
"duplicate mousedown listeners on every bfcache restore"
|
|
)
|
|
|
|
def test_new_calls_are_guarded_with_typeof(self):
|
|
"""New calls in the pageshow handler must be typeof-guarded for safe degradation."""
|
|
src = _boot_js()
|
|
handler_body = _pageshow_handler(src)
|
|
# Each of the new calls must be guarded
|
|
for fn in ("syncTopbar", "syncWorkspacePanelState", "startGatewaySSE",
|
|
"closeModelDropdown", "closeReasoningDropdown", "closeWsDropdown", "closeProfileDropdown"):
|
|
assert f"typeof {fn} === 'function'" in handler_body, (
|
|
f"{fn}() call in pageshow handler must be guarded with typeof === 'function'"
|
|
)
|
|
|
|
def test_pageshow_closes_all_dropdowns(self):
|
|
"""pageshow handler must close all known dropdowns to reset frozen bfcache popover state."""
|
|
src = _boot_js()
|
|
handler_body = _pageshow_handler(src)
|
|
for fn in ("closeModelDropdown", "closeReasoningDropdown", "closeWsDropdown", "closeProfileDropdown"):
|
|
assert fn in handler_body, (
|
|
f"pageshow handler must call {fn}() to dismiss any dropdown left open by bfcache"
|
|
)
|
|
|
|
def test_dropdowns_closed_before_layout_sync(self):
|
|
"""Dropdown closes must come before layout sync calls (clean state first)."""
|
|
src = _boot_js()
|
|
handler_body = _pageshow_handler(src)
|
|
close_idx = handler_body.find("closeModelDropdown")
|
|
sync_idx = handler_body.find("syncTopbar")
|
|
assert close_idx != -1 and sync_idx != -1, "Both close and sync calls must be present"
|
|
assert close_idx < sync_idx, (
|
|
"Dropdown close calls must appear before layout sync calls in the pageshow handler"
|
|
)
|
|
|