stackchain-dashboard/tests/test_today_wrap_up.py
timmy 468676d2f4
All checks were successful
CI / lint (pull_request) Successful in 2m56s
CI / build-release (pull_request) Successful in 6s
CI / browser-journey (pull_request) Successful in 1m50s
CI / release-candidate (pull_request) Has been skipped
feat: review wrap-up commitments in Today (Closes #992)
2026-08-16 23:15:00 +00:00

94 lines
4.6 KiB
Python

import json
import subprocess
from pathlib import Path
ROOT = Path(__file__).parents[1]
TODAY_WRAP_UP = ROOT / "frontend" / "today-wrap-up.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_wrap_up_schedules_selected_work_for_tomorrow_and_leaves_other_work_in_order():
script = f"""
const createWrapUp = require({json.dumps(str(TODAY_WRAP_UP))});
const items = [
{{kind:'issue', repository:'stackchain/dashboard', number:1, title:'First'}},
{{kind:'issue', repository:'stackchain/dashboard', number:2, title:'Second'}},
{{kind:'pull', repository:'stackchain/dashboard', number:3, title:'Third'}},
];
const ids = items.map(item => (item.kind || 'work') + ':' + item.repository + ':' + item.number + ':');
const today = [...ids];
const later = {{}};
const todayOps = [];
const laterOps = [];
const todayWork = {{
identity:item => (item.kind || 'work') + ':' + item.repository + ':' + item.number + ':',
read:() => [...today],
contains:item => today.includes((item.kind || 'work') + ':' + item.repository + ':' + item.number + ':'),
remove:item => {{
const id=(item.kind || 'work') + ':' + item.repository + ':' + item.number + ':';
const index=today.indexOf(id); if(index < 0) return false; today.splice(index,1); return true;
}},
}};
const laterWork = {{
identity:todayWork.identity,
presetUntil:preset => new Date('2026-08-17T09:00:00-04:00'),
defer:(item, until, options) => {{ later[todayWork.identity(item)] = until.toISOString(); laterOps.push([todayWork.identity(item), options]); return 'deferred'; }},
}};
const todaySync = {{enqueue:(action,id) => {{todayOps.push([action,id]); return true;}}, flush:()=>Promise.resolve(true)}};
const wrap = createWrapUp({{todayWork,laterWork,todaySync}});
wrap.open(items);
wrap.choose(ids[1], true);
const result = await wrap.finish();
process.stdout.write(JSON.stringify({{result,today,later,todayOps,laterOps}}));
"""
output = run_node(f"(async()=>{{{script}}})().catch(error=>{{console.error(error);process.exit(1);}})")
assert output["result"] == {"scheduled": 1, "left": 2, "wake_at": "2026-08-17T13:00:00.000Z"}
assert output["today"] == ["issue:stackchain/dashboard:1:", "pull:stackchain/dashboard:3:"]
assert output["later"] == {"issue:stackchain/dashboard:2:": "2026-08-17T13:00:00.000Z"}
assert output["todayOps"] == [["remove", "issue:stackchain/dashboard:2:"]]
assert output["laterOps"] == [["issue:stackchain/dashboard:2:", {"handoff": "today"}]]
def test_dashboard_packages_a_mobile_wrap_up_dialog_and_opens_it_after_recap_save():
from src import main
html = main.FRONTEND_BUILD.dashboard_html
css = (ROOT / "frontend" / "dashboard.css").read_text()
dashboard = (ROOT / "frontend" / "dashboard.js").read_text()
recap = (ROOT / "frontend" / "today-recap.js").read_text()
assert 'id="today-wrap-up-sheet" role="dialog"' in html
assert 'id="today-wrap-up-items"' in html
assert 'id="finish-today-wrap-up"' in html
assert 'static/today-wrap-up.js' in main.FRONTEND_BUILD.page_sources
assert 'setupTodayWrapUp({' in dashboard
assert 'todayWrapUpView.open' in dashboard
assert 'openWrapUp(handoff.actual_minutes)' in recap
assert '.today-wrap-up-actions button { min-height:44px;' in css
assert '.today-wrap-up-panel' in css and 'overflow-x:hidden' in css
assert 'tests/e2e/test_mobile_today_wrap_up_release.py' in (ROOT / '.gitea' / 'workflows' / 'ci.yml').read_text()
def test_wrap_up_repeat_confirmation_does_not_repeat_later_or_today_operations():
script = f"""
const createWrapUp = require({json.dumps(str(TODAY_WRAP_UP))});
const item={{kind:'issue',repository:'acme/mobile',number:41,title:'Ship'}};
const id='issue:acme/mobile:41:';
let present=true; let deferred=0; let removed=0;
const todayWork={{identity:()=>id,read:()=>present?[id]:[],contains:()=>present,remove:()=>{{present=false;return true;}}}};
const laterWork={{presetUntil:()=>new Date('2026-08-17T09:00:00Z'),defer:()=>{{deferred+=1;return 'deferred';}}}};
const todaySync={{enqueue:()=>{{removed+=1;return true;}},flush:()=>Promise.resolve(true)}};
const wrap=createWrapUp({{todayWork,laterWork,todaySync}});
wrap.open([item]); wrap.choose(id,true);
await wrap.finish(); await wrap.finish();
process.stdout.write(JSON.stringify({{deferred,removed,present}}));
"""
output = run_node(f"(async()=>{{{script}}})().catch(error=>{{console.error(error);process.exit(1);}})")
assert output == {"deferred": 1, "removed": 1, "present": False}