89 lines
3.4 KiB
Python
89 lines
3.4 KiB
Python
import json
|
|
import subprocess
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
from tests.dashboard_bundle import dashboard
|
|
|
|
|
|
POSITIONER = Path(__file__).resolve().parents[1] / "frontend" / "update-read-position.js"
|
|
|
|
|
|
def run_positioner(script: str) -> dict:
|
|
source = f"const createPositioner = require({json.dumps(str(POSITIONER))});\n" + script
|
|
result = subprocess.run(["node", "-e", source], capture_output=True, text=True)
|
|
assert result.returncode == 0, result.stderr
|
|
return json.loads(result.stdout)
|
|
|
|
|
|
def test_new_update_scrolls_to_newest_activity_once_without_reusing_previous_offset():
|
|
result = run_positioner("""
|
|
const listeners = {};
|
|
const panel = {scrollTop:480, addEventListener:(name, fn)=>listeners[name]=fn};
|
|
const jump = {hidden:true, focus(){}};
|
|
const first = {calls:0, scrollIntoView(){this.calls += 1}, classList:{add(){}}};
|
|
const second = {calls:0, scrollIntoView(){this.calls += 1}, classList:{add(){}}};
|
|
const positioner = createPositioner({panel, jump});
|
|
positioner.open('101');
|
|
positioner.ready('101', first);
|
|
positioner.ready('101', first);
|
|
panel.scrollTop = 920;
|
|
positioner.open('102');
|
|
positioner.ready('102', second);
|
|
process.stdout.write(JSON.stringify({first:first.calls, second:second.calls, jumpHidden:jump.hidden}));
|
|
""")
|
|
|
|
assert result == {"first": 1, "second": 1, "jumpHidden": True}
|
|
|
|
|
|
def test_user_scroll_during_loading_preserves_position_until_explicit_jump():
|
|
result = run_positioner("""
|
|
const listeners = {};
|
|
const panel = {scrollTop:120, addEventListener:(name, fn)=>listeners[name]=fn};
|
|
const jump = {hidden:true, focus(){}};
|
|
const target = {calls:0, scrollIntoView(){this.calls += 1}, classList:{add(){}}};
|
|
const positioner = createPositioner({panel, jump});
|
|
positioner.open('201');
|
|
panel.scrollTop = 260;
|
|
listeners.scroll();
|
|
positioner.ready('201', target);
|
|
const before = {calls:target.calls, hidden:jump.hidden, top:panel.scrollTop};
|
|
positioner.jump();
|
|
process.stdout.write(JSON.stringify({before, after:{calls:target.calls, hidden:jump.hidden}}));
|
|
""")
|
|
|
|
assert result == {
|
|
"before": {"calls": 0, "hidden": False, "top": 260},
|
|
"after": {"calls": 1, "hidden": True},
|
|
}
|
|
|
|
|
|
def test_stale_or_prefetched_conversation_cannot_move_the_visible_update():
|
|
result = run_positioner("""
|
|
const panel = {scrollTop:0, addEventListener(){}};
|
|
const jump = {hidden:true};
|
|
const stale = {calls:0, scrollIntoView(){this.calls += 1}, classList:{add(){}}};
|
|
const current = {calls:0, scrollIntoView(){this.calls += 1}, classList:{add(){}}};
|
|
const positioner = createPositioner({panel, jump});
|
|
positioner.open('302');
|
|
positioner.ready('301', stale);
|
|
positioner.ready('302', current);
|
|
process.stdout.write(JSON.stringify({stale:stale.calls, current:current.calls}));
|
|
""")
|
|
|
|
assert result == {"stale": 0, "current": 1}
|
|
|
|
|
|
@pytest.mark.anyio
|
|
async def test_dashboard_mounts_mobile_new_activity_positioning_flow():
|
|
html = await dashboard()
|
|
|
|
assert '<script src="static/update-read-position.js"></script>' in html
|
|
assert 'id="jump-update-new-activity"' in html
|
|
assert "updateReadPosition.open(String(item.notification_id))" in html
|
|
source = html + (POSITIONER.parent / "conversation.js").read_text()
|
|
assert "updateReadPosition.ready(String(getSelectedUpdate()?.notification_id || ''),newest)" in source
|
|
assert ".update-new-activity" in html
|
|
assert "BASE + 'static/update-read-position.js'" in (POSITIONER.parent / "service-worker.js").read_text()
|