feat: protect Today from urgent deadlines (Closes #721)
This commit is contained in:
1 parent
0d29b29bef
commit
fcc9ead33e
8 files changed
+195
-3
No files matched your search
@@ -0,0 +1,106 @@
|
||||
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
|
||||
@@ -722,6 +722,7 @@ def test_install_precaches_complete_subpath_scoped_app_shell():
|
||||
"/dashboard/static/offline-today.js",
|
||||
"/dashboard/static/my-work.js",
|
||||
"/dashboard/static/agenda-replan.js",
|
||||
"/dashboard/static/protect-today.js",
|
||||
"/dashboard/static/notification-undo.js",
|
||||
"/dashboard/static/card-planning.js",
|
||||
"/dashboard/static/work-selection.js",
|
||||
|
||||
Reference in new issue
Block a user