import json import subprocess from pathlib import Path ROOT = Path(__file__).parents[1] STATUS = ROOT / "frontend" / "live-data-status.js" HTML = ROOT / "frontend" / "index.html" CSS = ROOT / "frontend" / "dashboard.css" DASHBOARD = ROOT / "frontend" / "dashboard.js" def run_node(script: str) -> dict: result = subprocess.run( ["node", "-e", script], check=True, capture_output=True, text=True ) return json.loads(result.stdout) def test_live_data_status_summarizes_each_feed_without_claiming_live(): script = f""" const status = require({json.dumps(str(STATUS))}); const healthy = {{fresh_for_seconds:8, sections:{{ context:{{age_seconds:1}}, notifications:{{age_seconds:2}}, events:{{age_seconds:3}} }}}}; const oneDelayed = {{fresh_for_seconds:8, retry_in_seconds:30, sections:{{ context:{{age_seconds:1}}, notifications:{{age_seconds:14, stale:true, retry_in_seconds:30}}, events:{{age_seconds:3}} }}}}; const twoDelayed = {{fresh_for_seconds:8, sections:{{ context:{{age_seconds:12, degraded:true}}, notifications:{{age_seconds:14, stale:true}}, events:{{age_seconds:3}} }}}}; process.stdout.write(JSON.stringify({{ healthy:status.describe(healthy), one:status.describe(oneDelayed), two:status.describe(twoDelayed), unavailable:status.describe({{}}), }})); """ result = run_node(script) assert result["healthy"]["summary"] == "Live" assert [feed["state"] for feed in result["healthy"]["feeds"]] == ["live"] * 3 assert result["one"]["summary"] == "Updates delayed" assert result["one"]["nextRetrySeconds"] == 30 assert result["one"]["feeds"][1] == { "key": "notifications", "label": "Updates", "state": "delayed", "ageSeconds": 14 } assert result["two"]["summary"] == "2 data feeds delayed" assert result["unavailable"]["summary"] == "Live data unavailable" def test_live_data_status_controller_is_single_flight_and_reports_result(): script = f""" const status = require({json.dumps(str(STATUS))}); let resolveRefresh; let calls = 0; const states = []; const button = {{disabled:false}}; const output = {{textContent:''}}; const controller = status.createRefreshController({{ button, output, refresh:() => {{ calls += 1; return new Promise(resolve => {{ resolveRefresh = resolve; }}); }}, onState:value => states.push(value), }}); (async () => {{ const first = controller.run(); const second = controller.run(); const pending = {{calls, disabled:button.disabled, text:output.textContent}}; resolveRefresh({{context:{{}}}}); await Promise.all([first, second]); process.stdout.write(JSON.stringify({{pending, calls, disabled:button.disabled, text:output.textContent, states}})); }})(); """ assert run_node(script) == { "pending": {"calls": 1, "disabled": True, "text": "Refreshing live data…"}, "calls": 1, "disabled": False, "text": "Live data refreshed.", "states": ["refreshing", "success"], } def test_live_data_status_has_accessible_mobile_safe_sheet_contract(): html = HTML.read_text() css = CSS.read_text() dashboard = DASHBOARD.read_text() assert 'id="open-live-data-status"' in html assert 'aria-controls="live-data-status-sheet"' in html assert 'id="live-data-status-sheet"' in html assert 'aria-labelledby="live-data-status-heading"' in html assert 'id="live-data-status-feeds"' in html assert 'id="refresh-live-data"' in html assert '' in html assert ".live-data-status-panel" in css assert "width:min(560px,100%)" in css assert "min-height:44px" in css assert "liveDataStatus.describe" in dashboard assert "liveDataStatus.createRefreshController" in dashboard assert "contextPoller.getState()" in dashboard