174 lines
6.5 KiB
Python
174 lines
6.5 KiB
Python
import json
|
|
import subprocess
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
from tests.dashboard_bundle import dashboard
|
|
|
|
|
|
PLAN_TODAY = Path(__file__).parents[1] / "frontend" / "plan-today.js"
|
|
PLAN_TODAY_PREVIEW = Path(__file__).parents[1] / "frontend" / "plan-today-preview.js"
|
|
SERVICE_WORKER = Path(__file__).parents[1] / "frontend" / "service-worker.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_plan_today_drafts_capacity_order_and_cancel_without_writing():
|
|
script = f"""
|
|
const createPlanToday = require({json.dumps(str(PLAN_TODAY))});
|
|
const item = number => ({{kind:'issue', repository:'stackchain/dashboard', number, title:'Issue ' + number}});
|
|
const saved = [];
|
|
const planner = createPlanToday({{
|
|
identity: value => 'issue:stackchain/dashboard:' + value.number + ':',
|
|
limit: 3,
|
|
save: ids => saved.push(ids),
|
|
}});
|
|
planner.open([item(1), item(2)], [item(1), item(2), item(3), item(4)]);
|
|
const add = planner.toggle(item(3));
|
|
const full = planner.toggle(item(4));
|
|
const moved = planner.move('issue:stackchain/dashboard:3:', 'up');
|
|
const snapshot = planner.snapshot();
|
|
planner.cancel();
|
|
process.stdout.write(JSON.stringify({{add, full, moved, snapshot, afterCancel:planner.snapshot(), saved}}));
|
|
"""
|
|
assert run_node(script) == {
|
|
"add": "added",
|
|
"full": "full",
|
|
"moved": True,
|
|
"snapshot": {
|
|
"open": True,
|
|
"ids": [
|
|
"issue:stackchain/dashboard:1:",
|
|
"issue:stackchain/dashboard:3:",
|
|
"issue:stackchain/dashboard:2:",
|
|
],
|
|
"count": 3,
|
|
"limit": 3,
|
|
},
|
|
"afterCancel": {"open": False, "ids": [], "count": 0, "limit": 3},
|
|
"saved": [],
|
|
}
|
|
|
|
|
|
def test_plan_today_saves_exact_draft_and_starts_first_item_only_after_success():
|
|
script = f"""
|
|
const createPlanToday = require({json.dumps(str(PLAN_TODAY))});
|
|
const item = number => ({{kind:'issue', repository:'stackchain/dashboard', number, title:'Issue ' + number}});
|
|
const calls = [];
|
|
const planner = createPlanToday({{
|
|
identity: value => 'issue:stackchain/dashboard:' + value.number + ':',
|
|
save: ids => {{ calls.push(['save', ...ids]); return true; }},
|
|
start: first => calls.push(['start', first.number]),
|
|
}});
|
|
planner.open([item(1)], [item(1), item(2)]);
|
|
planner.toggle(item(2));
|
|
const result = planner.commit({{start:true}});
|
|
process.stdout.write(JSON.stringify({{result, calls, snapshot:planner.snapshot()}}));
|
|
"""
|
|
assert run_node(script) == {
|
|
"result": "saved",
|
|
"calls": [
|
|
["save", "issue:stackchain/dashboard:1:", "issue:stackchain/dashboard:2:"],
|
|
["start", 1],
|
|
],
|
|
"snapshot": {"open": False, "ids": [], "count": 0, "limit": 5},
|
|
}
|
|
|
|
|
|
def test_plan_today_preview_preserves_draft_scroll_and_adds_item_once_on_return():
|
|
script = f"""
|
|
const createPlanToday = require({json.dumps(str(PLAN_TODAY))});
|
|
const createPlanTodayPreview = require({json.dumps(str(PLAN_TODAY_PREVIEW))});
|
|
const item = number => ({{kind:'issue', repository:'stackchain/dashboard', number, title:'Issue ' + number}});
|
|
const events = [];
|
|
let scroll = 318;
|
|
const planner = createPlanToday({{
|
|
identity: value => 'issue:stackchain/dashboard:' + value.number + ':',
|
|
save: () => true,
|
|
}});
|
|
planner.open([item(1)], [item(1), item(2)]);
|
|
const preview = createPlanTodayPreview({{
|
|
planner,
|
|
identity: value => 'issue:stackchain/dashboard:' + value.number + ':',
|
|
getScroll: () => scroll,
|
|
setScroll: value => {{ scroll = value; }},
|
|
onOpen: value => events.push(['open', value.number]),
|
|
onClose: (value, trigger) => events.push(['close', value.number, trigger]),
|
|
}});
|
|
preview.open(item(2), 'preview-2');
|
|
scroll = 0;
|
|
const first = preview.close({{add:true}});
|
|
preview.open(item(2), 'preview-2');
|
|
scroll = 0;
|
|
const second = preview.close({{add:true}});
|
|
process.stdout.write(JSON.stringify({{first, second, scroll, events, planner:planner.snapshot()}}));
|
|
"""
|
|
assert run_node(script) == {
|
|
"first": "added",
|
|
"second": "already-added",
|
|
"scroll": 318,
|
|
"events": [
|
|
["open", 2],
|
|
["close", 2, "preview-2"],
|
|
["open", 2],
|
|
["close", 2, "preview-2"],
|
|
],
|
|
"planner": {
|
|
"open": True,
|
|
"ids": ["issue:stackchain/dashboard:1:", "issue:stackchain/dashboard:2:"],
|
|
"count": 2,
|
|
"limit": 5,
|
|
},
|
|
}
|
|
|
|
|
|
@pytest.mark.anyio
|
|
async def test_mobile_dashboard_wires_focused_plan_today_sheet():
|
|
html = await dashboard()
|
|
|
|
assert '<script src="static/plan-today.js"></script>' in html
|
|
assert 'id="plan-today"' in html
|
|
assert 'id="plan-today-sheet" role="dialog"' in html
|
|
assert 'id="plan-today-capacity"' in html
|
|
assert 'id="save-and-start-today"' in html
|
|
assert "const planToday = createPlanToday({" in html
|
|
assert "todaySync.enqueue('remove'" in html
|
|
assert "todaySync.enqueue('add'" in html
|
|
assert "qs('#plan-today').addEventListener('click'" in html
|
|
assert ".plan-today-actions { position:sticky; bottom:0;" in html
|
|
assert ".plan-today-actions button { min-height:44px;" in html
|
|
assert "padding-bottom:calc(12px + env(safe-area-inset-bottom))" in html
|
|
assert ".plan-today-item { grid-template-columns:1fr; }" in html
|
|
assert ".plan-today-item-actions { display:grid; grid-template-columns:repeat(3,1fr);" in html
|
|
assert ".plan-preview-actions { position:fixed; z-index:76;" in html
|
|
assert '<script src="static/plan-today-preview.js"></script>' in html
|
|
assert 'data-plan-preview="' in html
|
|
assert 'id="add-plan-preview"' in html
|
|
assert "taskOverlayHistory.open('plan-today-preview')" in html
|
|
assert "planTodayPreview.close({ add:true })" in html
|
|
assert "Preview unavailable offline" in html
|
|
|
|
|
|
@pytest.mark.anyio
|
|
async def test_plan_today_wires_cancel_back_and_success_through_overlay_history():
|
|
html = await dashboard()
|
|
|
|
assert "taskOverlayHistory.open('plan-today')" in html
|
|
assert "previous === 'plan-today' && kind !== 'plan-today'" in html
|
|
assert "kind === 'plan-today' && previous !== 'plan-today'" in html
|
|
assert "openPlanToday(planTodayTrigger, false)" in html
|
|
assert "closePlanToday(false)" in html
|
|
assert "if (result === 'saved') taskOverlayHistory.leave();" in html
|
|
|
|
|
|
def test_plan_today_controller_is_available_in_the_offline_shell():
|
|
source = SERVICE_WORKER.read_text()
|
|
|
|
assert "stackchain-dashboard-shell-v67" in source
|
|
assert "BASE + 'static/plan-today.js'" in source
|
|
assert "BASE + 'static/plan-today-preview.js'" in source
|