stackchain-dashboard/tests/test_protect_today.py
timmy fcc9ead33e
All checks were successful
CI / lint (pull_request) Successful in 1m26s
CI / build-release (pull_request) Successful in 5s
CI / release-candidate (pull_request) Has been skipped
feat: protect Today from urgent deadlines (Closes #721)
2026-08-13 08:27:01 +00:00

107 lines
4.2 KiB
Python

import json
import subprocess
from pathlib import Path
ROOT = Path(__file__).parents[1]
PROTECT_TODAY = ROOT / "frontend" / "protect-today.js"
def run_node(script: str) -> dict:
result = subprocess.run(["node", "-e", script], check=True, capture_output=True, text=True)
return json.loads(result.stdout)
def test_protect_today_prioritizes_and_deduplicates_urgent_deadlines_before_existing_plan():
script = f"""
const protectToday = require({json.dumps(str(PROTECT_TODAY))});
const issue = (number, group, due) => ({{
kind:'issue', repository:'stackchain/dashboard', number, title:'Issue ' + number,
is_assigned:true, state:'open', agenda_group:group, due_date:due,
}});
const urgentLater = issue(3, 'Today', '2026-08-13');
const urgentFirst = issue(2, 'Overdue', '2026-08-12');
const duplicate = issue(2, 'Overdue', '2026-08-12');
const existingUrgent = issue(1, 'Today', '2026-08-13');
const existingNormal = issue(8, null, null);
const result = protectToday.propose({{
agenda:[urgentLater, duplicate, urgentFirst, issue(4, 'Tomorrow', '2026-08-14')],
today:[existingNormal, existingUrgent],
identity:item => item.repository + '#' + item.number,
limit:5,
}});
process.stdout.write(JSON.stringify({{
selected:result.selected.map(item => item.number),
protected:result.protected.map(item => item.number),
displaced:result.displaced.map(item => item.number),
summary:result.summary,
}}));
"""
assert run_node(script) == {
"selected": [2, 1, 3, 8],
"protected": [2, 1, 3],
"displaced": [],
"summary": "3 urgent items protected · 0 displaced",
}
def test_protect_today_reports_existing_work_displaced_by_five_item_limit():
script = f"""
const protectToday = require({json.dumps(str(PROTECT_TODAY))});
const issue = (number, group = null) => ({{
kind:'issue', repository:'stackchain/dashboard', number, title:'Issue ' + number,
is_assigned:true, state:'open', agenda_group:group,
due_date:group === 'Overdue' ? '2026-08-12' : group === 'Today' ? '2026-08-13' : null,
}});
const result = protectToday.propose({{
agenda:[issue(10, 'Overdue'), issue(11, 'Today'), issue(12, 'Today')],
today:[issue(1), issue(2), issue(3), issue(4), issue(5)],
identity:item => item.repository + '#' + item.number,
limit:5,
}});
process.stdout.write(JSON.stringify({{
selected:result.selected.map(item => item.number),
displaced:result.displaced.map(item => item.number),
summary:result.summary,
}}));
"""
assert run_node(script) == {
"selected": [10, 11, 12, 1, 2],
"displaced": [3, 4, 5],
"summary": "3 urgent items protected · 3 displaced for review",
}
def test_protect_today_excludes_closed_unassigned_and_nonurgent_items():
script = f"""
const protectToday = require({json.dumps(str(PROTECT_TODAY))});
const items = [
{{kind:'issue',repository:'r',number:1,is_assigned:true,state:'closed',agenda_group:'Overdue',due_date:'2026-08-01'}},
{{kind:'issue',repository:'r',number:2,is_assigned:false,state:'open',agenda_group:'Today',due_date:'2026-08-13'}},
{{kind:'issue',repository:'r',number:3,is_assigned:true,state:'open',agenda_group:'Tomorrow',due_date:'2026-08-14'}},
{{kind:'pull',repository:'r',number:4,is_assigned:true,state:'open',agenda_group:'Overdue',due_date:'2026-08-01'}},
];
const result = protectToday.propose({{agenda:items,today:[],identity:item => item.repository + '#' + item.number}});
process.stdout.write(JSON.stringify(result));
"""
assert run_node(script) == {
"selected": [], "protected": [], "displaced": [],
"summary": "No overdue or due-today work needs protection.",
}
def test_dashboard_renders_mobile_protect_today_entrypoint_and_preview_context():
html = (ROOT / "frontend" / "index.html").read_text()
dashboard = (ROOT / "frontend" / "dashboard.js").read_text()
css = (ROOT / "frontend" / "dashboard.css").read_text()
bundle = (ROOT / "src" / "frontend_bundle.py").read_text()
assert 'id="protect-today"' in html
assert 'id="protect-today-status"' in html
assert "protectToday.propose" in dashboard
assert "pendingProtectToday" in dashboard
assert "Protect Today" in dashboard
assert ".protect-today" in css
assert "min-height:44px" in css.replace(" ", "")
assert '"static/protect-today.js"' in bundle