import json import subprocess from pathlib import Path import pytest from src.views import dashboard TODAY_WORK = Path(__file__).parents[1] / "frontend" / "today-work.js" def run_node(script): return subprocess.run( ["node", "-e", script], check=True, capture_output=True, text=True ).stdout def test_today_queue_is_account_scoped_ordered_unique_and_bounded(): script = f""" const createTodayWork = require({json.dumps(str(TODAY_WORK))}); const values = new Map(); const storage = {{ getItem: key => values.has(key) ? values.get(key) : null, setItem: (key, value) => values.set(key, value), removeItem: key => values.delete(key), }}; let login = 'timmy'; const queue = createTodayWork({{storage, getLogin: () => login, limit: 3}}); const issue = number => ({{kind:'issue', repository:'stackchain/dashboard', number, title:'Issue ' + number}}); const first = queue.add(issue(1)); const duplicate = queue.add(issue(1)); queue.add(issue(2)); queue.add(issue(3)); const full = queue.add(issue(4)); queue.move(issue(3), 'up'); queue.move(issue(3), 'up'); const timmy = queue.reconcile([issue(1), issue(2), issue(3), issue(4)]).map(item => item.number); const persisted = createTodayWork({{storage, getLogin: () => login, limit: 3}}) .reconcile([issue(1), issue(2), issue(3)]).map(item => item.number); login = 'alexander'; const isolated = queue.reconcile([issue(1), issue(2), issue(3)]).map(item => item.number); process.stdout.write(JSON.stringify({{first, duplicate, full, timmy, persisted, isolated}})); """ assert json.loads(run_node(script)) == { "first": "added", "duplicate": "exists", "full": "full", "timmy": [3, 1, 2], "persisted": [3, 1, 2], "isolated": [], } def test_today_queue_exposes_reorder_boundaries_for_touch_controls(): script = f""" const createTodayWork = require({json.dumps(str(TODAY_WORK))}); const values = new Map(); const storage = {{ getItem: key => values.get(key) || null, setItem: (key, value) => values.set(key, value), removeItem: key => values.delete(key), }}; const queue = createTodayWork({{storage, getLogin: () => 'timmy'}}); const item = number => ({{kind:'issue', repository:'stackchain/dashboard', number}}); queue.add(item(1)); queue.add(item(2)); queue.add(item(3)); process.stdout.write(JSON.stringify([ queue.position(item(1)), queue.position(item(2)), queue.position(item(3)), queue.position(item(9)) ])); """ assert json.loads(run_node(script)) == [ {"can_up": False, "can_down": True}, {"can_up": True, "can_down": True}, {"can_up": True, "can_down": False}, {"can_up": False, "can_down": False}, ] @pytest.mark.anyio async def test_dashboard_runs_the_curated_today_queue_as_a_mobile_work_flow(): html = await dashboard() assert '' in html assert 'data-work-filter="today"' in html assert 'data-work-count="today"' in html assert "const todayWork = createTodayWork({" in html assert "const filter = todayMyWork.length ? 'today' : (counts.attention ? 'attention' : 'all');" in html assert "selectedWorkFilter === 'today' ? todayMyWork : activeMyWork" in html assert 'data-today-add' in html assert 'data-today-remove' in html assert 'data-today-move="up"' in html assert 'data-today-move="down"' in html assert 'Today is limited to 5 items' in html assert '.today-actions button' in html and 'min-height:44px' in html @pytest.mark.anyio async def test_retained_authenticated_context_keeps_local_planning_separate_from_fresh_delivery(): html = await dashboard() assert "let planningOwnerLogin = '';" in html assert html.count("getLogin: () => planningOwnerLogin") == 2 assert "const retainedPlanningLogin = !snapshot.context.error ?" in html assert "planningOwnerLogin = retainedPlanningLogin;" in html assert "button.disabled = !planningOwnerLogin;" in html assert "activeFlushLogin = contextIdentityFresh ?" in html assert "getOwnerLogin: () => confirmedOwnerLogin" in html assert "Planning is unavailable until your operator identity is restored." in html assert "Could not save Today on this device." in html assert "Could not save Later on this device." in html assert "data-planning-disabled" in html