import json import subprocess from pathlib import Path CONTROLLER = Path(__file__).parents[1] / "frontend" / "today-week-reschedule.js" INDEX = CONTROLLER.parent / "index.html" CSS = CONTROLLER.parent / "dashboard.css" DASHBOARD = CONTROLLER.parent / "dashboard.js" BUNDLE = CONTROLLER.parents[1] / "src" / "frontend_bundle.py" def run_controller(scenario: str) -> dict: harness = f""" const createReschedule = require({json.dumps(str(CONTROLLER))}); (async()=>{{ {scenario} }})().catch(error=>{{console.error(error);process.exit(1);}}); """ completed = subprocess.run( ["node", "-e", harness], check=True, capture_output=True, text=True ) return json.loads(completed.stdout) def test_reschedule_controller_reviews_load_and_atomically_confirms_selected_day(): result = run_controller(""" const requests=[]; const weekState={revision:7,offline_snapshot:false,days:[]}; const review={days:[ {plan_date:'2026-08-24',label:'Mon, Aug 24',ids:['one'],planned_minutes:30,capacity_minutes:60,overloaded:false}, {plan_date:'2026-08-25',label:'Tue, Aug 25',ids:[],planned_minutes:0,capacity_minutes:90,overloaded:false} ]}; const week={load:async()=>weekState,review:()=>review,adopt:value=>{weekState.revision=value.revision;weekState.days=value.days;}}; const api=async(url,options)=>{ if(url==='api/v1/today')return {revision:4,ids:['active','other'],estimates:{active:45}}; requests.push({url,body:JSON.parse(options.body)});return { today:{revision:5,ids:['other'],capacity_minutes:120,estimates:{other:20}}, week:{revision:8,timezone:'UTC',days:[{plan_date:'2026-08-25',ids:['active'],capacity_minutes:90,estimates:{active:45}}]} };}; let adoptedToday=null; const controller=createReschedule({week,api,getToday:()=>({revision:4,ids:['active','other'],estimates:{active:45}}), adoptToday:value=>{adoptedToday=value;},operationId:()=> 'move-active'}); const opened=await controller.open('active'); const confirmed=await controller.confirm('2026-08-25',45); console.log(JSON.stringify({opened,confirmed,requests,adoptedToday,weekState})); """) assert result["opened"]["estimate_minutes"] == 45 assert result["opened"]["days"][0]["load"] == "30 / 60 min" assert result["opened"]["days"][1]["eligible"] is True assert result["requests"] == [{ "url": "api/v1/week/reschedule", "body": { "operation_id": "move-active", "identity": "active", "estimate_minutes": 45, "plan_date": "2026-08-25", "today_revision": 4, "week_revision": 7, "allow_over_capacity": False, }, }] assert result["adoptedToday"]["ids"] == ["other"] assert result["weekState"]["revision"] == 8 assert result["confirmed"]["today"]["revision"] == 5 def test_reschedule_controller_blocks_offline_full_and_overloaded_days_without_mutation(): result = run_controller(""" let calls=0; const days=[ {plan_date:'2026-08-24',label:'Mon',ids:['1','2','3','4','5'],planned_minutes:50,capacity_minutes:60}, {plan_date:'2026-08-25',label:'Tue',ids:['1'],planned_minutes:50,capacity_minutes:60} ]; const week={load:async()=>({revision:2,offline_snapshot:false}),review:()=>({days}),adopt:()=>{}}; const api=async url=>{if(url==='api/v1/today')return {revision:3,ids:['active'],estimates:{active:30}};calls++;}; const controller=createReschedule({week,api,getToday:()=>({revision:3,ids:['active'],estimates:{active:30}})}); const opened=await controller.open('active'); let fullError='',overError=''; try{await controller.confirm('2026-08-24',30);}catch(error){fullError=error.message;} try{await controller.confirm('2026-08-25',30);}catch(error){overError=error.message;} const offline=createReschedule({week:{load:async()=>({revision:2,offline_snapshot:true}),review:()=>({days})},api,getToday:()=>({revision:3,ids:['active'],estimates:{active:30}})}); let offlineError='';try{await offline.open('active');}catch(error){offlineError=error.message;} console.log(JSON.stringify({opened,fullError,overError,offlineError,calls})); """) assert result["opened"]["days"][0]["eligible"] is False assert result["fullError"] == "That Week Ahead day already has five items." assert result["overError"] == "That move exceeds the day's capacity. Confirm overload before rescheduling." assert result["offlineError"] == "Reconnect before rescheduling Today into Week Ahead." assert result["calls"] == 0 def test_mobile_active_today_reschedule_dialog_is_touch_safe_and_wired_into_release_bundle(): index = INDEX.read_text() css = CSS.read_text() dashboard = DASHBOARD.read_text() bundle = BUNDLE.read_text() assert 'data-work-session-reschedule-week' in index assert 'id="today-week-reschedule"' in index assert 'id="today-week-reschedule-days"' in index assert 'id="today-week-reschedule-estimate"' in index assert 'id="confirm-today-week-reschedule"' in index assert 'id="cancel-today-week-reschedule"' in index assert '.today-week-reschedule-days button { min-height:44px;' in css assert '.today-week-reschedule-panel { width:min(100%,560px);' in css assert 'x:currentTodayProgressTarget' in dashboard assert "runTodayTransition('next')" in dashboard assert '"static/today-week-reschedule.js"' in bundle