import json import subprocess from pathlib import Path HANDOFF = Path(__file__).parents[1] / "frontend" / "today-handoff.js" def run_node(script: str): result = subprocess.run(["node", "-e", script], capture_output=True, text=True) assert result.returncode == 0, result.stderr return json.loads(result.stdout) def test_due_wrap_up_commitments_are_reviewed_in_order_and_added_durably(): script = f""" const createHandoff=require({json.dumps(str(HANDOFF))}); const values=new Map(); const storage={{getItem:k=>values.get(k)||null,setItem:(k,v)=>values.set(k,v),removeItem:k=>values.delete(k)}}; const items=[ {{kind:'issue',repository:'r',number:2,title:'Second'}}, {{kind:'issue',repository:'r',number:1,title:'First'}}, ]; const identity=item=>'issue:'+item.repository+':'+item.number+':'; const today=[]; const operations=[]; const controller=createHandoff({{ storage,getLogin:()=> 'timmy',identity, todayWork:{{contains:item=>today.includes(identity(item)),add:item=>{{if(today.length>=1)return 'full';today.push(identity(item));return 'added';}}}}, todaySync:{{enqueue:(action,id)=>{{operations.push([action,id]);return true;}},flush:async()=>true}}, }}); controller.capture(['issue:r:1:','issue:r:2:','issue:r:1:']); const review=controller.review(items); controller.choose('issue:r:2:',false); const first=await controller.commit(items); controller.choose('issue:r:2:',true); const blocked=await controller.commit(items); process.stdout.write(JSON.stringify({{review,first,blocked,today,operations,pending:controller.pending()}})); """ result = run_node(f"(async()=>{{{script}}})().catch(error=>{{console.error(error);process.exit(1);}})") assert [row["identity"] for row in result["review"]] == ["issue:r:1:", "issue:r:2:"] assert all(row["selected"] for row in result["review"]) assert result["first"] == {"scheduled": 1, "blocked": 0, "remaining": 1} assert result["blocked"] == {"scheduled": 0, "blocked": 1, "remaining": 1} assert result["today"] == ["issue:r:1:"] assert result["operations"] == [["add", "issue:r:1:"]] assert result["pending"] == ["issue:r:2:"] def test_handoff_pending_state_is_account_scoped_and_capture_is_idempotent(): script = f""" const createHandoff=require({json.dumps(str(HANDOFF))}); const values=new Map();let login='timmy'; const storage={{getItem:k=>values.get(k)||null,setItem:(k,v)=>values.set(k,v),removeItem:k=>values.delete(k)}}; const options={{storage,getLogin:()=>login,identity:item=>item.id,todayWork:{{contains:()=>false,add:()=> 'added'}},todaySync:{{enqueue:()=>true,flush:async()=>true}}}}; const controller=createHandoff(options); controller.capture(['one','two']);controller.capture(['two','one']); const timmy=controller.pending();login='alexander';const isolated=controller.pending(); login='timmy';const resumed=createHandoff(options).pending(); process.stdout.write(JSON.stringify({{timmy,isolated,resumed}})); """ assert run_node(f"(async()=>{{{script}}})()") == { "timmy": ["one", "two"], "isolated": [], "resumed": ["one", "two"], } def test_dashboard_packages_accessible_mobile_commitment_review_and_account_sync(): from src import main html = main.FRONTEND_BUILD.dashboard_html dashboard = (HANDOFF.parents[0] / "dashboard.js").read_text() css = (HANDOFF.parents[0] / "dashboard.css").read_text() service_worker = (HANDOFF.parents[0] / "service-worker.js").read_text() assert 'id="today-handoff-dialog"' in html assert 'aria-labelledby="today-handoff-title"' in html assert 'id="today-handoff-items"' in html assert 'id="confirm-today-handoff"' in html assert '' in (HANDOFF.parents[0] / "index.html").read_text() assert "createTodayHandoff({" in dashboard assert "laterSync.enqueue(action, itemId, wakeAt, handoff)" in dashboard assert "todayHandoff.capture(handoffs)" in dashboard assert ".today-handoff-actions button { min-height:44px;" in css assert "BASE + 'static/today-handoff.js'" in service_worker assert "static/today-handoff.js" in main.FRONTEND_BUILD.page_sources