feat: refresh Week capacity with atomic reflow (Closes #1266)
This commit is contained in:
1 parent
3c35aa31c1
commit
24eb4d1e79
7 files changed
+134
-21
No files matched your search
@@ -334,12 +334,56 @@ console.log(JSON.stringify({staged,restored}));
|
||||
assert "private@example.com" not in json.dumps(result)
|
||||
|
||||
|
||||
def test_calendar_refresh_compares_load_and_applies_overloaded_capacity_with_one_atomic_reflow():
|
||||
result = run_import("""
|
||||
const elements=new Map();
|
||||
const element=(value='')=>({value,checked:false,hidden:false,textContent:'',innerHTML:'',disabled:false,files:[],listeners:{},
|
||||
addEventListener(name,listener){this.listeners[name]=listener;},focus(){}});
|
||||
for(const selector of ['#week-capacity-import','#week-capacity-review','#week-capacity-days','#week-capacity-status',
|
||||
'#week-capacity-file','#week-capacity-start','#week-capacity-end','#keep-week-free-times',
|
||||
'#apply-week-capacities','#cancel-week-capacity-import']) elements.set(selector,element());
|
||||
elements.get('#week-capacity-start').value='09:00';elements.get('#week-capacity-end').value='17:00';
|
||||
elements.get('#keep-week-free-times').checked=true;
|
||||
const dates=['2026-08-21','2026-08-22','2026-08-23','2026-08-24','2026-08-25','2026-08-26','2026-08-27'];
|
||||
const current=dates.map((date,index)=>({plan_date:date,capacity_minutes:480,planned_minutes:index===0?90:index===1?30:0,
|
||||
ids:index===0?['one','two']:index===1?['three']:[],estimates:index===0?{one:45,two:45}:index===1?{three:30}:{}}));
|
||||
let atomicCalls=0,ordinaryCalls=0,flushes=0,received=null;
|
||||
const controller={dates:()=>dates.map((date,index)=>({date,label:'Day '+(index+1)})),review:()=>({days:current}),
|
||||
previewCapacityReflow:values=>({blockers:[],unscheduled:[],days:values.map((value,index)=>({...value,
|
||||
ids:index===0?['one']:index===1?['two','three']:[],estimates:index===0?{one:45}:index===1?{two:45,three:30}:{}}))}),
|
||||
applyCapacityReflow:values=>{atomicCalls+=1;received=values;return {sync_pending:true};},
|
||||
stageCapacities:()=>{ordinaryCalls+=1;return {sync_pending:true};},flush:async()=>{flushes+=1;return {};}};
|
||||
const workflow=calendarImport.createWorkflow({controller,qs:selector=>elements.get(selector)});
|
||||
const source=`BEGIN:VCALENDAR\r\nBEGIN:VEVENT\r\nSUMMARY:Private meeting name\r\nATTENDEE:private@example.com\r\nDTSTART:20260821T090000\r\nDTEND:20260821T163000\r\nEND:VEVENT\r\nEND:VCALENDAR`;
|
||||
const reviewed=workflow.review(source);const label=elements.get('#apply-week-capacities').textContent;
|
||||
const markup=elements.get('#week-capacity-days').innerHTML;await workflow.apply();
|
||||
console.log(JSON.stringify({reviewed,label,markup,atomicCalls,ordinaryCalls,flushes,received}));
|
||||
""")
|
||||
|
||||
assert result["reviewed"][0]["capacity_minutes"] == 30
|
||||
assert "480 → 30 min available" in result["markup"]
|
||||
assert "90 min planned" in result["markup"]
|
||||
assert "60 min over refreshed capacity" in result["markup"]
|
||||
assert "Moves to Day 2" in result["markup"]
|
||||
assert result["label"] == "Apply capacities & reflow"
|
||||
assert result["atomicCalls"] == 1
|
||||
assert result["ordinaryCalls"] == 0
|
||||
assert result["flushes"] == 1
|
||||
assert result["received"][0]["free_windows"] == [
|
||||
{"start_time": "16:30", "end_time": "17:00"}
|
||||
]
|
||||
assert "Private meeting name" not in json.dumps(result)
|
||||
assert "private@example.com" not in json.dumps(result)
|
||||
|
||||
|
||||
def test_week_ahead_exposes_private_calendar_capacity_import_in_the_packaged_planning_flow():
|
||||
html = (FRONTEND / "index.html").read_text()
|
||||
dashboard = (FRONTEND / "dashboard.js").read_text()
|
||||
css = (FRONTEND / "dashboard.css").read_text()
|
||||
|
||||
assert 'id="open-week-capacity-import"' in html
|
||||
assert '>Refresh from calendar</button>' in html
|
||||
assert '<h2 id="week-capacity-title">Refresh Week Ahead availability</h2>' in html
|
||||
assert 'id="week-capacity-import"' in html
|
||||
assert 'id="week-capacity-file"' in html and 'accept=".ics,text/calendar"' in html
|
||||
assert 'id="week-capacity-start"' in html and 'id="week-capacity-end"' in html
|
||||
|
||||
@@ -311,6 +311,35 @@ console.log(JSON.stringify({before,preview,unchanged,applied,writes,pending:week
|
||||
assert result["pending"]["days"] == result["preview"]["days"]
|
||||
|
||||
|
||||
def test_week_controller_atomically_reflows_against_refreshed_calendar_capacity_and_keeps_windows():
|
||||
result = run_controller("""
|
||||
const values=new Map();let writes=0;
|
||||
const storage={getItem:key=>values.get(key)||null,setItem:(key,value)=>{writes+=1;values.set(key,value);},removeItem:key=>values.delete(key)};
|
||||
const week=createWeekPlan({storage,getLogin:()=> 'timmy',fetchJson:async()=>({}),
|
||||
localDate:()=> '2026-08-20',timeZone:()=> 'UTC'});
|
||||
week.adopt({revision:6,timezone:'UTC',days:[
|
||||
{plan_date:'2026-08-21',ids:['one','two'],capacity_minutes:120,estimates:{one:45,two:45},free_windows:[{start_time:'09:00',end_time:'11:00'}]},
|
||||
{plan_date:'2026-08-22',ids:['three'],capacity_minutes:120,estimates:{three:30}},
|
||||
{plan_date:'2026-08-23',ids:[],capacity_minutes:120,estimates:{}}
|
||||
]});
|
||||
const capacities=week.dates().map((item,index)=>({plan_date:item.date,capacity_minutes:index===0?45:120,
|
||||
free_windows:index===0?[{start_time:'10:00',end_time:'10:45'}]:[{start_time:'09:00',end_time:'11:00'}]}));
|
||||
const before=week.state();const preview=week.previewCapacityReflow(capacities);const unchanged=week.state();
|
||||
const applied=week.applyCapacityReflow(capacities);
|
||||
console.log(JSON.stringify({before,preview,unchanged,applied,writes,pending:week.pending()}));
|
||||
""")
|
||||
|
||||
assert result["unchanged"] == result["before"]
|
||||
assert result["writes"] == 1
|
||||
assert result["applied"]["sync_pending"] is True
|
||||
assert [day["ids"] for day in result["preview"]["days"][:2]] == [["one"], ["two", "three"]]
|
||||
assert result["preview"]["days"][0]["free_windows"] == [
|
||||
{"start_time": "10:00", "end_time": "10:45"}
|
||||
]
|
||||
assert result["pending"]["days"] == result["preview"]["days"]
|
||||
assert [item for day in result["pending"]["days"] for item in day["ids"]] == ["one", "two", "three"]
|
||||
|
||||
|
||||
def test_week_controller_reflow_never_places_more_than_five_items_on_a_day():
|
||||
result = run_controller("""
|
||||
const week=createWeekPlan({fetchJson:async()=>({}),localDate:()=> '2026-08-20',timeZone:()=> 'UTC'});
|
||||
|
||||
Reference in new issue
Block a user