Closes #856. Co-authored-by: Frank Song <138988108+franksong2702@users.noreply.github.com> Reviewed-by: nesquena (709bd37 — test isolation fix also included)
69 lines
3.7 KiB
Python
69 lines
3.7 KiB
Python
"""Regression checks for #856 pinned-star layout in the session list."""
|
|
|
|
from pathlib import Path
|
|
|
|
|
|
SESSIONS_JS = (Path(__file__).resolve().parent.parent / "static" / "sessions.js").read_text()
|
|
STYLE_CSS = (Path(__file__).resolve().parent.parent / "static" / "style.css").read_text()
|
|
|
|
|
|
def test_pinned_indicator_renders_inside_title_row():
|
|
title_row_idx = SESSIONS_JS.find("titleRow.className='session-title-row';")
|
|
assert title_row_idx != -1, "session title row construction not found"
|
|
|
|
pin_idx = SESSIONS_JS.find("pinInd.className='session-pin-indicator';", title_row_idx)
|
|
assert pin_idx != -1, "pinned indicator creation not found after title row"
|
|
|
|
append_to_title_row_idx = SESSIONS_JS.find("titleRow.appendChild(pinInd);", pin_idx)
|
|
assert append_to_title_row_idx != -1, "pinned indicator should be appended to titleRow"
|
|
|
|
append_to_el_idx = SESSIONS_JS.find("el.appendChild(pinInd);", pin_idx)
|
|
assert append_to_el_idx == -1, (
|
|
"pinned indicator should not be appended to the outer session row; "
|
|
"it must align inside the title row with the spinner/unread indicator"
|
|
)
|
|
|
|
|
|
def test_pinned_indicator_uses_fixed_indicator_box():
|
|
assert ".session-pin-indicator{" in STYLE_CSS, "session pin indicator CSS block missing"
|
|
css_block = STYLE_CSS[STYLE_CSS.find(".session-pin-indicator{"):STYLE_CSS.find(".session-pin-indicator svg{")]
|
|
assert "width:10px;" in css_block, "pin indicator should reserve a fixed 10px width"
|
|
assert "height:10px;" in css_block, "pin indicator should reserve a fixed 10px height"
|
|
assert "justify-content:center;" in css_block, "pin indicator should center the star inside its box"
|
|
|
|
|
|
def test_state_indicator_always_appended_to_prevent_layout_shift():
|
|
"""State span is always added to the DOM (visibility:hidden when inactive) to prevent
|
|
titles shifting left/right when the spinner or unread dot appears/disappears."""
|
|
title_row_idx = SESSIONS_JS.find("titleRow.className='session-title-row';")
|
|
assert title_row_idx != -1, "title row construction not found"
|
|
|
|
# state span must be appended unconditionally (no surrounding if-check)
|
|
append_idx = SESSIONS_JS.find("titleRow.appendChild(state);", title_row_idx)
|
|
assert append_idx != -1, "state span must always be appended to titleRow"
|
|
|
|
# Verify CSS uses visibility:hidden to reserve the slot
|
|
assert "session-state-indicator{" in STYLE_CSS, "session-state-indicator CSS rule missing"
|
|
base_block_start = STYLE_CSS.find("session-state-indicator{")
|
|
base_block_end = STYLE_CSS.find("}", base_block_start)
|
|
base_block = STYLE_CSS[base_block_start:base_block_end]
|
|
assert "visibility:hidden;" in base_block, (
|
|
"session-state-indicator should default to visibility:hidden so it reserves slot "
|
|
"without being visible — prevents title layout shift on state changes"
|
|
)
|
|
|
|
|
|
def test_apperror_path_calls_render_session_list():
|
|
"""apperror handler must call renderSessionList() to clear the streaming indicator
|
|
immediately rather than waiting for the 5s streaming poll interval."""
|
|
messages_js = (Path(__file__).resolve().parent.parent / "static" / "messages.js").read_text()
|
|
apperror_idx = messages_js.find("source.addEventListener('apperror'")
|
|
assert apperror_idx != -1, "apperror handler not found in messages.js"
|
|
warning_idx = messages_js.find("source.addEventListener('warning'", apperror_idx)
|
|
assert warning_idx != -1, "warning handler not found after apperror handler"
|
|
apperror_block = messages_js[apperror_idx:warning_idx]
|
|
assert "renderSessionList()" in apperror_block, (
|
|
"apperror handler must call renderSessionList() so the streaming indicator "
|
|
"clears immediately on server errors, not after a 5s poll delay"
|
|
)
|