138 lines
5.6 KiB
Python
138 lines
5.6 KiB
Python
|
|
"""Tests for #822 — session list empty after browser reload / version update.
|
||
|
|
|
||
|
|
Root cause (from Opus analysis): Chrome's bfcache restores a prior search query
|
||
|
|
into `#sessionSearch` on page restore; `renderSessionListFromCache()` reads that
|
||
|
|
field and applies it as a title filter — hiding every session.
|
||
|
|
|
||
|
|
Fix:
|
||
|
|
- ``autocomplete="off"`` on the input hints to browsers not to restore the value
|
||
|
|
- Boot-time explicit `sessionSearch.value = ''` before the first render covers
|
||
|
|
fresh loads and hard reloads
|
||
|
|
- ``pageshow`` listener that checks ``event.persisted`` covers the true bfcache
|
||
|
|
restore case (where the async boot IIFE does NOT re-run)
|
||
|
|
"""
|
||
|
|
import pathlib
|
||
|
|
import re
|
||
|
|
|
||
|
|
REPO = pathlib.Path(__file__).parent.parent
|
||
|
|
|
||
|
|
|
||
|
|
def read(rel):
|
||
|
|
return (REPO / rel).read_text(encoding='utf-8')
|
||
|
|
|
||
|
|
|
||
|
|
class TestSessionSearchAutocompleteAttribute:
|
||
|
|
"""index.html must opt the session search input out of browser autocomplete/restore."""
|
||
|
|
|
||
|
|
def test_session_search_has_autocomplete_off(self):
|
||
|
|
src = read('static/index.html')
|
||
|
|
m = re.search(r'<input[^>]*id="sessionSearch"[^>]*>', src)
|
||
|
|
assert m, "#sessionSearch input tag not found in index.html"
|
||
|
|
tag = m.group(0)
|
||
|
|
assert 'autocomplete="off"' in tag or "autocomplete='off'" in tag, (
|
||
|
|
"#sessionSearch must have autocomplete=\"off\" so browsers do not "
|
||
|
|
"restore a prior search query across reloads or bfcache restores (#822)"
|
||
|
|
)
|
||
|
|
|
||
|
|
|
||
|
|
class TestBootClearsSessionSearch:
|
||
|
|
"""Boot sequence must clear any browser-restored search value before
|
||
|
|
the first render, so the initial `renderSessionListFromCache` call sees
|
||
|
|
an empty filter and shows all sessions."""
|
||
|
|
|
||
|
|
def test_boot_clears_session_search_value_before_first_render(self):
|
||
|
|
src = read('static/boot.js')
|
||
|
|
# Must find a line that sets sessionSearch.value = '' at boot
|
||
|
|
assert re.search(
|
||
|
|
r"getElementById\(['\"]sessionSearch['\"]\)\s*;\s*if\s*\([^)]+\)\s*[^=]+\.value\s*=\s*['\"]{2}"
|
||
|
|
r"|sessionSearch[^=]+\.value\s*=\s*['\"]{2}",
|
||
|
|
src,
|
||
|
|
), (
|
||
|
|
"boot.js must clear #sessionSearch.value to '' before the first render "
|
||
|
|
"(before renderSessionList / loadSession) to avoid stale filter from "
|
||
|
|
"browser form restoration (#822)"
|
||
|
|
)
|
||
|
|
|
||
|
|
def test_boot_clear_is_before_first_render_call(self):
|
||
|
|
"""The clear must precede the first renderSessionList call path so the
|
||
|
|
initial render shows an unfiltered list."""
|
||
|
|
src = read('static/boot.js')
|
||
|
|
clear_pos = None
|
||
|
|
m = re.search(r"getElementById\(['\"]sessionSearch['\"]\)", src)
|
||
|
|
if m:
|
||
|
|
clear_pos = m.start()
|
||
|
|
assert clear_pos is not None, "session search clear not found in boot.js"
|
||
|
|
first_render_pos = src.find('renderSessionList()', clear_pos)
|
||
|
|
assert first_render_pos != -1, "renderSessionList() call not found after clear"
|
||
|
|
assert clear_pos < first_render_pos, (
|
||
|
|
"sessionSearch clear must appear before the first renderSessionList() call"
|
||
|
|
)
|
||
|
|
|
||
|
|
|
||
|
|
class TestPageShowBfcacheHandler:
|
||
|
|
"""bfcache restore path: the async boot IIFE does NOT re-run when the
|
||
|
|
browser restores the page from back-forward cache, but the DOM — including
|
||
|
|
any stale value in #sessionSearch — IS restored. A `pageshow` listener
|
||
|
|
with `event.persisted` check is the only reliable way to clear on bfcache."""
|
||
|
|
|
||
|
|
def test_pageshow_listener_registered(self):
|
||
|
|
src = read('static/boot.js')
|
||
|
|
assert re.search(
|
||
|
|
r"addEventListener\(\s*['\"]pageshow['\"]",
|
||
|
|
src,
|
||
|
|
), (
|
||
|
|
"boot.js must register a `pageshow` event listener to handle "
|
||
|
|
"bfcache-restored page views (#822)"
|
||
|
|
)
|
||
|
|
|
||
|
|
def test_pageshow_handler_checks_event_persisted(self):
|
||
|
|
"""Only bfcache restores set event.persisted=true; fresh loads have
|
||
|
|
it false and are already handled by the boot IIFE. Guarding prevents
|
||
|
|
clearing the search on every page show (which would wipe an in-progress
|
||
|
|
user filter if any other pageshow triggers happen)."""
|
||
|
|
src = read('static/boot.js')
|
||
|
|
m = re.search(
|
||
|
|
r"addEventListener\(\s*['\"]pageshow['\"].*?\}\s*\)",
|
||
|
|
src,
|
||
|
|
re.DOTALL,
|
||
|
|
)
|
||
|
|
assert m, "pageshow listener body not found"
|
||
|
|
body = m.group(0)
|
||
|
|
assert 'persisted' in body, (
|
||
|
|
"pageshow handler must guard on event.persisted so fresh loads "
|
||
|
|
"don't double-clear the field"
|
||
|
|
)
|
||
|
|
|
||
|
|
def test_pageshow_handler_clears_session_search(self):
|
||
|
|
src = read('static/boot.js')
|
||
|
|
m = re.search(
|
||
|
|
r"addEventListener\(\s*['\"]pageshow['\"].*?\}\s*\)",
|
||
|
|
src,
|
||
|
|
re.DOTALL,
|
||
|
|
)
|
||
|
|
assert m
|
||
|
|
body = m.group(0)
|
||
|
|
assert 'sessionSearch' in body, (
|
||
|
|
"pageshow handler must target #sessionSearch specifically"
|
||
|
|
)
|
||
|
|
assert re.search(r"\.value\s*=\s*['\"]{2}", body), (
|
||
|
|
"pageshow handler must set sessionSearch.value = ''"
|
||
|
|
)
|
||
|
|
|
||
|
|
def test_pageshow_handler_triggers_rerender(self):
|
||
|
|
"""After clearing on bfcache restore, the cached DOM still shows the
|
||
|
|
filtered view. Re-rendering from cache with the now-empty filter
|
||
|
|
repopulates the list."""
|
||
|
|
src = read('static/boot.js')
|
||
|
|
m = re.search(
|
||
|
|
r"addEventListener\(\s*['\"]pageshow['\"].*?\}\s*\)",
|
||
|
|
src,
|
||
|
|
re.DOTALL,
|
||
|
|
)
|
||
|
|
assert m
|
||
|
|
body = m.group(0)
|
||
|
|
assert 'renderSessionListFromCache' in body or 'renderSessionList' in body, (
|
||
|
|
"pageshow handler must re-render the list after clearing the filter "
|
||
|
|
"so the stale filtered DOM is replaced with the full list"
|
||
|
|
)
|