diff --git a/frontend/today-week-reschedule.js b/frontend/today-week-reschedule.js index 38cfa4d..750a522 100644 --- a/frontend/today-week-reschedule.js +++ b/frontend/today-week-reschedule.js @@ -337,10 +337,9 @@ function mountTodayWeekReschedule({ dialog.addEventListener('cancel',event=>{event.preventDefault();close();}); confirm.addEventListener('click',async()=>{ if(!selectedDate)return; - confirm.disabled=true;status.textContent='Moving Today into Week Ahead…'; + confirm.disabled=true;status.textContent='Moving Today into Week Ahead…';dialog.close(); try{ const result=await controller.confirm(selectedDate,Number(estimate.value),{allowOverload}); - dialog.close(); if(result.sync_pending){ announce('Moved locally. Saved on this device · sync pending.');warm(); }else{ @@ -348,6 +347,7 @@ function mountTodayWeekReschedule({ } await continueToday(); }catch(error){ + if(!dialog.open)dialog.showModal(); const overload=error.message.includes('Confirm overload');allowOverload=overload; status.textContent=error.message; confirm.textContent=overload?'Confirm overload & continue':'Move to Week Ahead & continue'; diff --git a/tests/test_today_week_reschedule.py b/tests/test_today_week_reschedule.py index 00babb4..3dd76c9 100644 --- a/tests/test_today_week_reschedule.py +++ b/tests/test_today_week_reschedule.py @@ -326,6 +326,73 @@ console.log(JSON.stringify({message,adopted,pending:controller.pending(),remaini assert result["remaining"] == 1 +def run_mounted_confirmation(*, fail_move: bool = False) -> dict: + failure = "true" if fail_move else "false" + scenario = """ +const mount=require('./frontend/today-week-reschedule.js').mount; +class Element { + constructor(){this.listeners={};this.dataset={};this.children=[];this.open=false;this.disabled=false;this.value='';this.textContent='';} + addEventListener(type,handler){(this.listeners[type] ||= []).push(handler);} + async emit(type,event={preventDefault(){}}){for(const handler of this.listeners[type]||[])await handler(event);} + append(...children){this.children.push(...children);} + replaceChildren(...children){this.children=children;} + querySelectorAll(selector){return selector==='button' ? this.children : [];} + querySelector(){return this.children.find(child=>!child.disabled)||null;} + setAttribute(){} + showModal(){this.open=true;} + close(){this.open=false;} + focus(){} + click(){return this.emit('click');} +} +const selectors={ + '#today-week-reschedule':new Element(), '#today-week-reschedule-days':new Element(), + '#today-week-reschedule-estimate':new Element(), '#today-week-reschedule-status':new Element(), + '#confirm-today-week-reschedule':new Element(), '[data-work-session-reschedule-week]':new Element(), + '#cancel-today-week-reschedule':new Element(), '[data-mobile-today-more]':new Element(), +}; +const document={hidden:false,addEventListener(){},createElement:()=>new Element()}; +const window={addEventListener(){},setTimeout(){}}; +let finishMove,failMove; +const move=new Promise((resolve,reject)=>{finishMove=resolve;failMove=reject;}); +const today={revision:4,ids:['active'],estimates:{active:30}}; +const weekState={revision:7,offline_snapshot:false,days:[]}; +const week={load:async()=>weekState,review:()=>({days:[{ + plan_date:'2026-08-27',label:'Thu',ids:[],planned_minutes:0,capacity_minutes:90,eligible:true, +}]}),adopt(){}}; +const api=async(url)=>url==='api/v1/today' ? today : move; +mount({qs:selector=>selectors[selector],document,window,week,api,getToday:()=>today, + adoptToday(){},currentTarget:()=>({identity:'active'}),closeActions(){},refresh:async()=>{},warm(){}, + continueToday:async()=>{},announce(){},schedule:callback=>callback()}); +await selectors['[data-work-session-reschedule-week]'].emit('click'); +await selectors['#today-week-reschedule-days'].children[0].emit('click'); +const confirming=selectors['#confirm-today-week-reschedule'].emit('click'); +await Promise.resolve(); +const openWhileSaving=selectors['#today-week-reschedule'].open; +if(__FAIL_MOVE__){ + const error=new Error('Move unavailable. Try again.');error.status=500;failMove(error); +}else{ + finishMove({today:{revision:5,ids:[],estimates:{}},week:{revision:8,days:[]}}); +} +await confirming; +console.log(JSON.stringify({openWhileSaving,openAfterSettled:selectors['#today-week-reschedule'].open})); +""" + return run_controller(scenario.replace("__FAIL_MOVE__", failure)) + + +def test_reschedule_dialog_closes_as_soon_as_a_valid_move_is_confirmed(): + assert run_mounted_confirmation() == { + "openWhileSaving": False, + "openAfterSettled": False, + } + + +def test_reschedule_dialog_reopens_with_retry_state_when_confirm_fails(): + assert run_mounted_confirmation(fail_move=True) == { + "openWhileSaving": False, + "openAfterSettled": True, + } + + def test_mobile_active_today_reschedule_dialog_is_touch_safe_and_wired_into_release_bundle(): index = INDEX.read_text() css = CSS.read_text()