84 lines
3.9 KiB
Python
84 lines
3.9 KiB
Python
|
|
"""Regression test for #3696 — `_sessionAttentionState is not defined`.
|
||
|
|
|
||
|
|
Bug: `_sessionAttentionState` was declared INSIDE `renderSessionListFromCache()`
|
||
|
|
and relied on "function hoisting", but the separate top-level function
|
||
|
|
`_sidebarRowHasVisibleMessages` (reached via renderSessionListFromCache ->
|
||
|
|
_partitionSidebarSessionRows) called it BARE. Function hoisting is scoped to the
|
||
|
|
enclosing function, so the call threw `ReferenceError: _sessionAttentionState is
|
||
|
|
not defined` on every sidebar cache-render — the session list went blank
|
||
|
|
(v0.51.269, regressed by #3672 when _sidebarRowHasVisibleMessages was extracted
|
||
|
|
to top level).
|
||
|
|
|
||
|
|
Fix: hoist `_sessionAttentionState` to top-level (module/global) scope so both the
|
||
|
|
top-level visibility predicate and the nested per-row renderer can reach it.
|
||
|
|
|
||
|
|
This is a structural test (no node/eslint needed, runs in every shard). The
|
||
|
|
behavioral scope-analysis guard lives in tests/test_static_js_scope_undef.py +
|
||
|
|
scripts/scope_undef_gate.py, which catch the whole class. This test pins the
|
||
|
|
specific #3696 invariant cheaply.
|
||
|
|
"""
|
||
|
|
import re
|
||
|
|
from pathlib import Path
|
||
|
|
|
||
|
|
SESSIONS_JS = (Path(__file__).resolve().parents[1] / "static" / "sessions.js").read_text(encoding="utf-8")
|
||
|
|
|
||
|
|
|
||
|
|
def _brace_body(src: str, open_brace_idx: int) -> tuple[int, int]:
|
||
|
|
"""Return (start, end) char offsets of the body delimited by the brace at
|
||
|
|
open_brace_idx (exclusive of the braces)."""
|
||
|
|
depth = 1
|
||
|
|
i = open_brace_idx + 1
|
||
|
|
while i < len(src) and depth:
|
||
|
|
if src[i] == "{":
|
||
|
|
depth += 1
|
||
|
|
elif src[i] == "}":
|
||
|
|
depth -= 1
|
||
|
|
i += 1
|
||
|
|
return open_brace_idx + 1, i - 1
|
||
|
|
|
||
|
|
|
||
|
|
def _function_span(src: str, name: str) -> tuple[int, int]:
|
||
|
|
m = re.search(r"function\s+" + re.escape(name) + r"\s*\(", src)
|
||
|
|
assert m, f"function {name} not found"
|
||
|
|
brace = src.find("{", m.end())
|
||
|
|
return _brace_body(src, brace)
|
||
|
|
|
||
|
|
|
||
|
|
def test_session_attention_state_is_top_level():
|
||
|
|
"""`_sessionAttentionState` must be declared at top-level scope (column 0),
|
||
|
|
not nested inside another function — otherwise the top-level callers throw
|
||
|
|
ReferenceError (#3696)."""
|
||
|
|
decls = re.findall(r"^(\s*)function\s+_sessionAttentionState\s*\(", SESSIONS_JS, re.M)
|
||
|
|
assert decls, "_sessionAttentionState declaration not found"
|
||
|
|
assert len(decls) == 1, f"expected exactly one declaration, found {len(decls)}"
|
||
|
|
assert decls[0] == "", (
|
||
|
|
"#3696: _sessionAttentionState must be a TOP-LEVEL function (no leading "
|
||
|
|
f"indentation), but it is indented {len(decls[0])} spaces (nested). A nested "
|
||
|
|
"declaration only hoists within its enclosing function, so top-level callers "
|
||
|
|
"like _sidebarRowHasVisibleMessages throw 'ReferenceError: _sessionAttentionState "
|
||
|
|
"is not defined'."
|
||
|
|
)
|
||
|
|
|
||
|
|
|
||
|
|
def test_session_attention_state_not_nested_in_render_from_cache():
|
||
|
|
"""Belt-and-suspenders: the definition must NOT live inside the body of
|
||
|
|
`renderSessionListFromCache` (where it was when #3696 shipped)."""
|
||
|
|
start, end = _function_span(SESSIONS_JS, "renderSessionListFromCache")
|
||
|
|
body = SESSIONS_JS[start:end]
|
||
|
|
assert "function _sessionAttentionState(" not in body, (
|
||
|
|
"#3696: _sessionAttentionState is defined inside renderSessionListFromCache() — "
|
||
|
|
"it must be hoisted to top-level so _sidebarRowHasVisibleMessages can call it."
|
||
|
|
)
|
||
|
|
|
||
|
|
|
||
|
|
def test_sidebar_visibility_predicate_calls_attention_state():
|
||
|
|
"""Guard the regression's trigger: _sidebarRowHasVisibleMessages (top-level)
|
||
|
|
references _sessionAttentionState. If this call is ever removed the bug can't
|
||
|
|
recur, but while it exists the function MUST be top-level (asserted above)."""
|
||
|
|
start, end = _function_span(SESSIONS_JS, "_sidebarRowHasVisibleMessages")
|
||
|
|
body = SESSIONS_JS[start:end]
|
||
|
|
assert "_sessionAttentionState(" in body, (
|
||
|
|
"_sidebarRowHasVisibleMessages no longer calls _sessionAttentionState — if this "
|
||
|
|
"is intentional, update this test; the #3696 invariant assumes this call exists."
|
||
|
|
)
|