feat: add focused mobile Today planner (#385)
This commit is contained in:
1 parent
19122c69e6
commit
de6c9c249a
11 files changed
+344
-11
No files matched your search
@@ -347,5 +347,5 @@ async def test_dashboard_syncs_every_later_change_and_exposes_account_status():
|
||||
def test_later_sync_ships_atomically_in_the_offline_shell():
|
||||
source = (Path(__file__).parents[1] / "frontend" / "service-worker.js").read_text()
|
||||
|
||||
assert "stackchain-dashboard-shell-v56" in source
|
||||
assert "stackchain-dashboard-shell-v57" in source
|
||||
assert "BASE + 'static/later-sync.js'" in source
|
||||
@@ -137,4 +137,4 @@ def test_markdown_work_bodies_are_mobile_safe_block_containers():
|
||||
assert ".markdown-content { min-width:0; max-width:100%; overflow-wrap:anywhere;" in css
|
||||
assert ".markdown-content pre { max-width:100%; overflow-x:auto;" in css
|
||||
assert ".markdown-content a { min-height:44px;" in css
|
||||
assert "stackchain-dashboard-shell-v56" in worker
|
||||
assert "stackchain-dashboard-shell-v57" in worker
|
||||
@@ -35,4 +35,4 @@ def test_offline_shell_contains_every_local_dashboard_runtime_asset():
|
||||
shell_assets = set(re.findall(r"BASE \+ '([^']+)'", worker.split("async function sessionCsrf", 1)[0]))
|
||||
|
||||
assert local_assets <= shell_assets, f"Offline shell is missing: {sorted(local_assets - shell_assets)}"
|
||||
assert "stackchain-dashboard-shell-v56" in worker
|
||||
assert "stackchain-dashboard-shell-v57" in worker
|
||||
@@ -0,0 +1,105 @@
|
||||
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"
|
||||
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},
|
||||
}
|
||||
|
||||
|
||||
@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
|
||||
|
||||
|
||||
def test_plan_today_controller_is_available_in_the_offline_shell():
|
||||
source = SERVICE_WORKER.read_text()
|
||||
|
||||
assert "stackchain-dashboard-shell-v57" in source
|
||||
assert "BASE + 'static/plan-today.js'" in source
|
||||
@@ -108,7 +108,7 @@ async function dispatchNotificationClick(route) {{
|
||||
def test_duplicate_aware_capture_ships_in_a_new_offline_shell():
|
||||
source = WORKER.read_text()
|
||||
|
||||
assert "stackchain-dashboard-shell-v56" in source
|
||||
assert "stackchain-dashboard-shell-v57" in source
|
||||
assert "BASE + 'static/create-issue-sheet.js'" in source
|
||||
assert "BASE + 'static/dashboard.js'" in source
|
||||
|
||||
@@ -116,14 +116,14 @@ def test_duplicate_aware_capture_ships_in_a_new_offline_shell():
|
||||
def test_exact_later_picker_ships_atomically_in_a_new_offline_shell():
|
||||
source = WORKER.read_text()
|
||||
|
||||
assert "stackchain-dashboard-shell-v56" in source
|
||||
assert "stackchain-dashboard-shell-v57" in source
|
||||
assert "BASE + 'static/later-picker.js'" in source
|
||||
|
||||
|
||||
def test_navigation_deadline_ships_in_a_new_shell_cache():
|
||||
source = WORKER.read_text()
|
||||
|
||||
assert "stackchain-dashboard-shell-v56" in source
|
||||
assert "stackchain-dashboard-shell-v57" in source
|
||||
assert "BASE + 'static/dashboard.css'" in source
|
||||
assert "BASE + 'static/dashboard.js'" in source
|
||||
assert "BASE + 'static/install-app.js'" in source
|
||||
@@ -132,21 +132,21 @@ def test_navigation_deadline_ships_in_a_new_shell_cache():
|
||||
def test_today_convergence_ships_in_a_new_shell_cache():
|
||||
source = WORKER.read_text()
|
||||
|
||||
assert "stackchain-dashboard-shell-v56" in source
|
||||
assert "stackchain-dashboard-shell-v57" in source
|
||||
assert "BASE + 'static/today-sync.js'" in source
|
||||
|
||||
|
||||
def test_mobile_search_viewport_ships_in_a_new_offline_shell():
|
||||
source = WORKER.read_text()
|
||||
|
||||
assert "stackchain-dashboard-shell-v56" in source
|
||||
assert "stackchain-dashboard-shell-v57" in source
|
||||
assert "BASE + 'static/mobile-search-viewport.js'" in source
|
||||
|
||||
|
||||
def test_update_ownership_flow_ships_atomically_in_a_new_offline_shell():
|
||||
source = WORKER.read_text()
|
||||
|
||||
assert "stackchain-dashboard-shell-v56" in source
|
||||
assert "stackchain-dashboard-shell-v57" in source
|
||||
assert "BASE + 'static/update-ownership.js'" in source
|
||||
|
||||
|
||||
@@ -341,6 +341,7 @@ def test_install_precaches_complete_subpath_scoped_app_shell():
|
||||
"/dashboard/static/offline-today.js",
|
||||
"/dashboard/static/my-work.js",
|
||||
"/dashboard/static/today-work.js",
|
||||
"/dashboard/static/plan-today.js",
|
||||
"/dashboard/static/today-sync.js",
|
||||
"/dashboard/static/update-ownership.js",
|
||||
"/dashboard/static/later-work.js",
|
||||
|
||||
@@ -86,7 +86,7 @@ sync.enqueue('add', 'issue:r:1:');
|
||||
def test_inflight_today_drain_ships_in_a_new_offline_shell():
|
||||
source = (Path(__file__).parents[1] / "frontend" / "service-worker.js").read_text()
|
||||
|
||||
assert "stackchain-dashboard-shell-v56" in source
|
||||
assert "stackchain-dashboard-shell-v57" in source
|
||||
assert "BASE + 'static/today-sync.js'" in source
|
||||
|
||||
|
||||
|
||||
Reference in new issue
Block a user