107 lines
4.2 KiB
Python
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
|