101 lines
3.9 KiB
Python
101 lines
3.9 KiB
Python
import json
|
|
import subprocess
|
|
from pathlib import Path
|
|
|
|
|
|
FRONTEND = Path(__file__).parents[1] / "frontend"
|
|
CONTROLLER = FRONTEND / "tomorrow-plan.js"
|
|
INDEX = FRONTEND / "index.html"
|
|
CSS = FRONTEND / "dashboard.css"
|
|
|
|
|
|
def run_controller(scenario: str) -> dict:
|
|
harness = f"""
|
|
const createTomorrowPlan = 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_tomorrow_planner_loads_and_saves_independently_from_today():
|
|
result = run_controller("""
|
|
const requests=[];
|
|
const fetchJson=async (url, options={})=>{
|
|
requests.push({url,method:options.method||'GET',body:options.body ? JSON.parse(options.body) : null});
|
|
if ((options.method||'GET') === 'GET') return {
|
|
revision:2, ids:['issue:r:2:'], capacity_minutes:90,
|
|
estimates:{'issue:r:2:':45}, plan_date:'2026-08-20', timezone:'UTC'
|
|
};
|
|
return {revision:3,...JSON.parse(options.body)};
|
|
};
|
|
const planner=createTomorrowPlan({fetchJson, localDate:()=> '2026-08-19', timeZone:()=> 'UTC'});
|
|
const loaded=await planner.load();
|
|
const saved=await planner.save({ids:['issue:r:3:'],capacity_minutes:120,estimates:{'issue:r:3:':60}});
|
|
console.log(JSON.stringify({loaded,saved,requests}));
|
|
""")
|
|
|
|
assert result["loaded"]["ids"] == ["issue:r:2:"]
|
|
assert result["saved"]["plan_date"] == "2026-08-20"
|
|
assert result["requests"] == [
|
|
{"url": "api/v1/tomorrow", "method": "GET", "body": None},
|
|
{
|
|
"url": "api/v1/tomorrow",
|
|
"method": "PUT",
|
|
"body": {
|
|
"base_revision": 2,
|
|
"ids": ["issue:r:3:"],
|
|
"capacity_minutes": 120,
|
|
"estimates": {"issue:r:3:": 60},
|
|
"plan_date": "2026-08-20",
|
|
"timezone": "UTC",
|
|
},
|
|
},
|
|
]
|
|
assert all(request["url"] != "api/v1/today" for request in result["requests"])
|
|
|
|
|
|
def test_tomorrow_planner_promotes_only_on_matching_local_date():
|
|
result = run_controller("""
|
|
const requests=[];
|
|
const fetchJson=async (url, options={})=>{
|
|
requests.push({url,body:options.body ? JSON.parse(options.body) : null});
|
|
return {ids:['issue:r:2:'],revision:8};
|
|
};
|
|
const before=createTomorrowPlan({fetchJson,localDate:()=> '2026-08-19',timeZone:()=> 'UTC',createId:()=> 'rollover-id'});
|
|
before.adopt({revision:3,ids:['issue:r:2:'],capacity_minutes:60,estimates:{},plan_date:'2026-08-20',timezone:'UTC'});
|
|
const early=await before.promote(7);
|
|
const due=createTomorrowPlan({fetchJson,localDate:()=> '2026-08-20',timeZone:()=> 'UTC',createId:()=> 'rollover-id'});
|
|
due.adopt({revision:3,ids:['issue:r:2:'],capacity_minutes:60,estimates:{},plan_date:'2026-08-20',timezone:'UTC'});
|
|
const promoted=await due.promote(7);
|
|
console.log(JSON.stringify({early,promoted,requests}));
|
|
""")
|
|
|
|
assert result["early"] is False
|
|
assert result["promoted"]["revision"] == 8
|
|
assert result["requests"] == [{
|
|
"url": "api/v1/tomorrow/promote",
|
|
"body": {
|
|
"promotion_id": "rollover-id",
|
|
"tomorrow_revision": 3,
|
|
"today_revision": 7,
|
|
},
|
|
}]
|
|
|
|
|
|
def test_tomorrow_plan_has_a_touch_safe_mobile_entry_and_reuses_the_ordered_planner():
|
|
index = INDEX.read_text()
|
|
css = CSS.read_text()
|
|
dashboard = (FRONTEND / "dashboard.js").read_text()
|
|
|
|
assert '<button class="plan-tomorrow" id="plan-tomorrow" type="button">Plan Tomorrow</button>' in index
|
|
assert ".plan-tomorrow { min-height:44px;" in css
|
|
assert '<script src="static/tomorrow-plan.js"></script>' in index
|
|
assert "const tomorrowPlan = createTomorrowPlan" in dashboard
|
|
assert "qs('#plan-tomorrow').addEventListener('click'" in dashboard
|
|
assert "planningTomorrow ? saveTomorrowPlan(plan) : saveTodayPlan(plan)" in dashboard
|
|
assert "tomorrowPlan.load()" in dashboard
|
|
assert "tomorrowPlan.promote(plan.revision)" in dashboard
|