import json import subprocess from pathlib import Path ROOT = Path(__file__).parents[1] MODULE = ROOT / "frontend" / "search-preview.js" def run(script: str) -> dict: harness = f""" require({json.dumps(str(MODULE))}); const createDetailWatch = globalThis.createDetailWatch; const state = {{ requests:[], renders:[], refreshes:0 }}; const feature = createDetailWatch({{ fetchJson: async (path, options={{}}) => {{ state.requests.push({{path,method:options.method || 'GET'}}); return {{watching:false,following_synced:true}}; }}, refreshFollowing: async () => {{ state.refreshes += 1; }}, onState: (status,watching,error) => state.renders.push({{status,watching,error:error?.message}}), }}); (async () => {{ {script} }})().catch(error => {{ console.error(error); process.exit(1); }}); """ completed = subprocess.run(["node", "-e", harness], text=True, capture_output=True, check=True) return json.loads(completed.stdout) def test_detail_watch_loads_authoritative_typed_subscription_state(): result = run(""" await feature.open({repository:'stackchain/api',kind:'pull',number:84,state:'open'}); process.stdout.write(JSON.stringify(state)); """) assert result["requests"] == [{ "path": "api/v1/repos/stackchain/api/issues/84/preview/subscription?kind=pull", "method": "GET", }] assert result["renders"] == [ {"status": "loading", "watching": False}, {"status": "ready", "watching": False}, ] assert result["refreshes"] == 0 def test_detail_watch_mutation_is_single_flight_and_refreshes_following_after_confirmation(): script = f""" require({json.dumps(str(MODULE))}); const createDetailWatch = globalThis.createDetailWatch; const state={{requests:[],renders:[],refreshes:0}}; let finishMutation; const feature=createDetailWatch({{ fetchJson:async (path,options={{}}) => {{ state.requests.push({{path,method:options.method || 'GET'}}); if (!options.method) return {{watching:false}}; return new Promise(resolve => {{finishMutation=() => resolve({{watching:true,following_synced:true,following_count:3}});}}); }}, refreshFollowing:async () => {{state.refreshes += 1;}}, onState:(status,watching,error) => state.renders.push({{status,watching,error:error?.message}}), }}); (async () => {{ const item={{repository:'stackchain/api',kind:'issue',number:42,state:'open'}}; await feature.open(item); const first=feature.toggle(); const second=feature.toggle(); await new Promise(resolve => setImmediate(resolve)); state.samePromise=first === second; finishMutation(); await Promise.all([first,second]); process.stdout.write(JSON.stringify(state)); }})().catch(error => {{console.error(error);process.exit(1);}}); """ result = json.loads(subprocess.run( ["node", "-e", script], text=True, capture_output=True, check=True ).stdout) assert result["samePromise"] is True assert [request["method"] for request in result["requests"]] == ["GET", "PUT"] assert result["refreshes"] == 1 assert result["renders"][-2]["status"] == "watching" assert result["renders"][-1]["status"] == "watched" assert result["renders"][-1]["watching"] is True def test_detail_watch_partial_sync_never_claims_success_or_refreshes_following(): script = f""" require({json.dumps(str(MODULE))}); const createDetailWatch = globalThis.createDetailWatch; const state={{renders:[],refreshes:0}}; const feature=createDetailWatch({{ fetchJson:async (_path,options={{}}) => options.method ? {{watching:true,following_synced:false,error:'Following could not sync.'}} : {{watching:false}}, refreshFollowing:async () => {{state.refreshes += 1;}}, onState:(status,watching,error) => state.renders.push({{status,watching,error:error?.message}}), }}); (async () => {{ await feature.open({{repository:'stackchain/api',kind:'issue',number:42,state:'open'}}); try {{ await feature.toggle(); }} catch (error) {{ state.rejected=error.message; }} process.stdout.write(JSON.stringify(state)); }})().catch(error => {{console.error(error);process.exit(1);}}); """ result = json.loads(subprocess.run( ["node", "-e", script], text=True, capture_output=True, check=True ).stdout) assert result["rejected"] == "Following could not sync." assert result["refreshes"] == 0 assert result["renders"][-1]["status"] == "error" assert result["renders"][-1]["watching"] is False assert all(state["status"] != "watched" for state in result["renders"]) def test_detail_watch_is_wired_into_both_mobile_work_details_and_bundle(): html = (ROOT / "frontend" / "index.html").read_text() dashboard = (ROOT / "frontend" / "dashboard.js").read_text() css = (ROOT / "frontend" / "dashboard.css").read_text() bundle = (ROOT / "src" / "frontend_bundle.py").read_text() workflow = (ROOT / ".gitea" / "workflows" / "ci.yml").read_text() search_preview = (ROOT / "frontend" / "search-preview.js").read_text() assert '' in html assert '' in html assert 'id="issue-watch-status"' in html assert 'id="pull-watch-status"' in html assert 'static/search-preview.js' in bundle assert 'root.createDetailWatch' in search_preview assert 'function bindDetailWatch(' in dashboard assert 'issueDetailWatch.open(item)' in dashboard assert 'pullDetailWatch.open(item)' in dashboard assert '#watch-issue-detail' in css assert '#watch-pull-detail' in css assert "python3 -m pytest tests/e2e -q" in workflow assert (ROOT / "tests/e2e/test_mobile_detail_watch_release.py").is_file() def test_detail_watch_ignores_a_late_subscription_response_for_the_previous_item(): script = f""" require({json.dumps(str(MODULE))}); const createDetailWatch = globalThis.createDetailWatch; const state={{renders:[]}}; const completions={{}}; const feature=createDetailWatch({{ fetchJson:path => new Promise(resolve => {{completions[path]=resolve;}}), onState:(status,watching,error) => state.renders.push({{status,watching,error:error?.message}}), }}); (async () => {{ const first=feature.open({{repository:'stackchain/api',kind:'issue',number:1,state:'open'}}); const second=feature.open({{repository:'stackchain/api',kind:'issue',number:2,state:'open'}}); completions[Object.keys(completions)[1]]({{watching:true}}); await second; completions[Object.keys(completions)[0]]({{watching:false}}); await first; process.stdout.write(JSON.stringify(state)); }})().catch(error => {{console.error(error);process.exit(1);}}); """ result = json.loads(subprocess.run( ["node", "-e", script], text=True, capture_output=True, check=True ).stdout) ready = [state for state in result["renders"] if state["status"] == "ready"] assert len(ready) == 1 assert ready[0]["watching"] is True def test_detail_watch_reports_subscription_load_failure_as_recoverable_error(): script = f""" require({json.dumps(str(MODULE))}); const state={{renders:[]}}; const feature=globalThis.createDetailWatch({{ fetchJson:async () => {{throw new Error('temporarily unavailable');}}, onState:(status,watching,error) => state.renders.push({{status,watching,error:error?.message}}), }}); (async () => {{ try {{ await feature.open({{repository:'stackchain/api',kind:'issue',number:1,state:'open'}}); }} catch (error) {{ state.rejected=error.message; }} process.stdout.write(JSON.stringify(state)); }})().catch(error => {{console.error(error);process.exit(1);}}); """ result = json.loads(subprocess.run( ["node", "-e", script], text=True, capture_output=True, check=True ).stdout) assert result["rejected"] == "temporarily unavailable" assert result["renders"][-1] == { "status": "error", "watching": False, "error": "temporarily unavailable" }