HERMES-GIELIE/tests/test_issue3162_ensure_messages_loaded.py

44 lines
2 KiB
Python
Raw Normal View History

"""Regression test pinning the #3162 fix.
#3162: `_ensureMessagesLoaded` in static/sessions.js declared `const msgs` but the
#3018 ephemeral-field carry-forward reassigns it (`msgs = window._carryForwardEphemeralTurnFields(...)`).
`const` -> runtime TypeError -> "Failed to load conversation messages" toast on every
mobile message (v0.51.161-166). Fix: `const` -> `let`.
This is the targeted pin; tests/test_static_js_runtime_lint.py is the general guard.
"""
from pathlib import Path
REPO = Path(__file__).resolve().parents[1]
SESSIONS_JS = (REPO / "static" / "sessions.js").read_text(encoding="utf-8")
def _ensure_messages_loaded_body() -> str:
start = SESSIONS_JS.index("async function _ensureMessagesLoaded")
# Window widened (#3326 added reload-width-hint handling inside this function,
# pushing the carry-forward reassignment further down; #3790 added the
# cold-load expand_renderable param + comment, pushing it further still).
fix(webui): keep stale transcript mounted while hidden-tab return reloads new messages (#5177) After a hidden interval that added persisted messages (post-turn bg-review writes, sibling-tab writes, the just-finished main turn flushing), switching the tab back caused refreshActiveSessionIfExternallyUpdated('visible'|'focus') to hit remoteCount !== localCount and call loadSession(sid, {force:true}), which synchronously did S.messages = [] before awaiting the metadata + messages fetches. The visible result was the entire conversation transcript blanking for the round-trip and reappearing — '对话突然消失,重刷才回来'. sessions.js itself warns about this 'disappear and reappear' tradeoff at the top of the remoteCount/remoteLast guard, but only short-circuits the metadata-only branch (#5061). #5122's 4-probe staged reconnect covers a different path (SSE error with ready_state=2 on a visible tab) and does not apply when the SSE error arrives while visibility_state='hidden' — that bottoms out through _deferStreamErrorIfPageHidden before the reconnect block ever runs. Fix: - refreshActiveSessionIfExternallyUpdated maps the visibility/focus recovery reasons ({visible, focus}) to a new keepStaleUntilLoaded option, forwarded to loadSession. The post-stream idle reconcile and the poll/external paths keep their existing behaviour. - loadSession ANDs opts.keepStaleUntilLoaded with sameSessionForceReload (so cross-session switches still clear synchronously — leaving a prior session's transcript on screen during a navigation is the original bug the clear was written for) and, on that path, skips the synchronous S.messages/S.toolCalls/_messagesTruncated/_oldestIdx clear. The new transcript is SWAPPED into S.messages by _ensureMessagesLoaded(sid, {force:true}), so the user sees old DOM directly replaced by new DOM in a single render frame. - _ensureMessagesLoaded grows an opts.force escape hatch so its 'messages already populated' early-return cannot skip the swap when stale messages are still in place. Verified live on a source 8701 build: - before: minHtmlObserved 65874, minKidsObserved 11 — 372 samples at 16ms, DOM html length NEVER dipped below the pre-call value during the reload. - loadSession was called with force:true keepStale:true reason:'visible', outcome 'reloaded', sMsgs 21 → 1037, after kids 11 → 134 (single-frame swap). Tests: - tests/test_issue5177_hidden_tab_blank_gap.py (new, 7 cases): source-locks the keep-stale guard shape, the recovery-reason map, the _ensureMessagesLoaded opts.force escape hatch, and that the synchronous clear is wrapped in if(!_keepStaleUntilLoaded). - Updated the two existing source-lock tests whose anchor strings included the loadSession call signature (tests/test_webui_external_refresh_frontend.py + tests/test_tars_scroll_reset_regressions.py) to match the new loadSession(... keepStaleUntilLoaded:_keepStaleUntilLoaded) form. No behavioural assertions changed. Local: node --check static/sessions.js OK. 122 passed across test_issue5177_hidden_tab_blank_gap.py + the full adjacent set (external-refresh / journal-frontend / scroll-reset / reconnect-chronology / inflight-stream-reuse / sse-error-multi-probe / issue4295 / issue4856).
2026-06-29 04:51:31 +00:00
return SESSIONS_JS[start: start + 4500]
def test_ensure_messages_loaded_declares_msgs_with_let():
body = _ensure_messages_loaded_body()
assert "let msgs = (data.session.messages" in body, (
"_ensureMessagesLoaded must declare `let msgs` — it is reassigned by the #3018 "
"carry-forward, and `const` throws a runtime TypeError on every mobile message (#3162)"
)
assert "const msgs = (data.session.messages" not in body, (
"found `const msgs` in _ensureMessagesLoaded — this is the #3162 brick-class bug; "
"must be `let` because msgs is reassigned"
)
def test_ensure_messages_loaded_reassignment_still_present():
"""Keep this test meaningful: confirm the reassignment that requires `let` exists.
If the carry-forward is removed, revisit whether `let` is still needed."""
body = _ensure_messages_loaded_body().replace(" ", "")
assert "msgs=window._carryForwardEphemeralTurnFields" in body, (
"the #3018 carry-forward reassignment of msgs is gone — re-evaluate the let/const "
"decision in _ensureMessagesLoaded"
)