import json import os import subprocess from pathlib import Path import pytest from tests.dashboard_bundle import dashboard @pytest.mark.anyio async def test_empty_all_work_queue_offers_find_and_create_actions(): markup = (Path(__file__).resolve().parents[1] / "frontend" / "index.html").read_text() source = await dashboard() assert 'id="empty-work-start"' in markup assert 'Ready for something new?' in markup assert 'id="empty-work-find"' in markup assert 'id="empty-work-create"' in markup assert "qs('#empty-work-find').addEventListener('click'" in source assert "qs('#find-work').click()" in source assert "qs('#empty-work-create').addEventListener('click'" in source assert "qs('#new-issue').click()" in source assert "emptyWorkStart.hidden = !showEmptyStart" in source MY_WORK = Path(__file__).parents[1] / "frontend" / "my-work.js" NOTIFICATION_UNDO = Path(__file__).parents[1] / "frontend" / "notification-undo.js" TODAY_TIMER = Path(__file__).parents[1] / "frontend" / "today-timer.js" LATER_WORK = Path(__file__).parents[1] / "frontend" / "later-work.js" DETAIL_DEFER = Path(__file__).parents[1] / "frontend" / "detail-defer.js" LATER_PICKER = Path(__file__).parents[1] / "frontend" / "later-picker.js" LATER_AND_START = Path(__file__).parents[1] / "frontend" / "later-and-start.js" REVIEW_SHEET = Path(__file__).parents[1] / "frontend" / "review-sheet.js" ISSUE_SHEET = Path(__file__).parents[1] / "frontend" / "issue-sheet.js" CREATE_ISSUE_SHEET = Path(__file__).parents[1] / "frontend" / "create-issue-sheet.js" PULL_SHEET = Path(__file__).parents[1] / "frontend" / "pull-sheet.js" CONVERSATION = Path(__file__).parents[1] / "frontend" / "conversation.js" COMMENT_ACTIONS = Path(__file__).parents[1] / "frontend" / "comment-actions.js" PICK_WORK = Path(__file__).parents[1] / "frontend" / "pick-work.js" WORK_ROUTE = Path(__file__).parents[1] / "frontend" / "work-route.js" UPDATE_OWNERSHIP = Path(__file__).parents[1] / "frontend" / "update-ownership.js" AGENDA_SESSION_LAUNCHER = Path(__file__).parents[1] / "frontend" / "agenda-session-launcher.js" UPDATE_TRIAGE_LAUNCHER = Path(__file__).parents[1] / "frontend" / "update-triage-launcher.js" def test_update_triage_launch_waits_for_complete_single_flight_discovery(): script = f""" const createLauncher = require({json.dumps(str(UPDATE_TRIAGE_LAUNCHER))}); let release; let discoveries = 0; let opens = 0; const messages = []; const launcher = createLauncher({{ selectUpdates: () => {{}}, discover: () => {{ discoveries += 1; return new Promise(resolve => {{ release = resolve; }}); }}, isUpdatesSelected: () => true, hasMore: () => false, hasCheckpoint: () => false, resume: () => {{ throw new Error('must start'); }}, start: () => {{ opens += 1; return true; }}, announce: message => messages.push(message), }}); const first = launcher.open(); const second = launcher.open(); Promise.resolve().then(() => {{ const before = {{discoveries, opens, same:first === second}}; release(true); return Promise.all([first, second]).then(results => process.stdout.write(JSON.stringify({{before, discoveries, opens, results, messages}})) ); }}); """ result = subprocess.run(["node", "-e", script], capture_output=True, text=True) assert result.returncode == 0, result.stderr assert json.loads(result.stdout) == { "before": {"discoveries": 1, "opens": 0, "same": True}, "discoveries": 1, "opens": 1, "results": ["opened", "opened"], "messages": ["Checking all unread updates…"], } def test_update_triage_launch_preserves_checkpoint_when_discovery_fails(): script = f""" const createLauncher = require({json.dumps(str(UPDATE_TRIAGE_LAUNCHER))}); const messages = []; let resumed = 0; const launcher = createLauncher({{ selectUpdates: () => {{}}, discover: async () => false, isUpdatesSelected: () => true, hasMore: () => true, hasCheckpoint: () => true, resume: () => {{ resumed += 1; return true; }}, start: () => true, announce: message => messages.push(message), }}); launcher.open().then(result => process.stdout.write(JSON.stringify({{result, resumed, messages}}))); """ result = subprocess.run(["node", "-e", script], capture_output=True, text=True) assert result.returncode == 0, result.stderr assert json.loads(result.stdout) == { "result": "incomplete", "resumed": 0, "messages": [ "Checking all unread updates…", "Updates check paused. Retry to check older unread updates.", ], } def test_update_triage_launch_applies_atomic_snapshot_before_starting(): script = f""" const createLauncher = require({json.dumps(str(UPDATE_TRIAGE_LAUNCHER))}); const events = []; const snapshot = {{items:[{{id:1}},{{id:51}}],total:2,complete:true}}; const launcher = createLauncher({{ selectUpdates: () => events.push('selected'), discover: async () => snapshot, applySnapshot: value => events.push(['applied', value.items.map(item => item.id)]), isUpdatesSelected: () => true, hasMore: () => false, hasCheckpoint: () => false, resume: () => {{ throw new Error('must start'); }}, start: () => {{ events.push('started'); return true; }}, announce: () => {{}}, }}); launcher.open().then(result => process.stdout.write(JSON.stringify({{result, events}}))); """ result = subprocess.run(["node", "-e", script], capture_output=True, text=True) assert result.returncode == 0, result.stderr assert json.loads(result.stdout) == { "result": "opened", "events": ["selected", ["applied", [1, 51]], "started"], } def test_notification_pager_load_all_retries_from_failed_page_without_duplicates(): script = f""" const buildMyWork = require({json.dumps(str(MY_WORK))}); let items = [{{id:1}}]; let fail = true; const requested = []; const pager = buildMyWork.createNotificationPager({{ load: async page => {{ requested.push(page); if (page === 2 && fail) {{ fail = false; throw new Error('offline'); }} return page === 2 ? {{page:2,total:101,has_more:true,items:[{{id:1}},{{id:51}}]}} : {{page:3,total:101,has_more:false,items:[{{id:101}}]}}; }}, onNotifications: value => {{ items = value; }}, onPagination: () => {{}}, onStatus: () => {{}}, }}); pager.reset({{page:1,total:101,has_more:true}}); pager.loadAll(() => items).then(firstResult => pager.loadAll(() => items).then(secondResult => process.stdout.write(JSON.stringify({{firstResult, secondResult, requested, ids:items.map(item => item.id)}})) )); """ result = subprocess.run(["node", "-e", script], capture_output=True, text=True) assert result.returncode == 0, result.stderr assert json.loads(result.stdout) == { "firstResult": False, "secondResult": True, "requested": [2, 2, 3], "ids": [1, 51, 101], } def test_agenda_session_launch_waits_for_complete_single_flight_discovery(): script = f""" const createLauncher = require({json.dumps(str(AGENDA_SESSION_LAUNCHER))}); let finishDiscovery; let discoveries = 0; let opens = 0; let queue = 'agenda'; const launcher = createLauncher({{ selectAgenda: () => {{ queue = 'agenda'; }}, discover: () => {{ discoveries += 1; return new Promise(resolve => {{ finishDiscovery = resolve; }}); }}, isAgendaSelected: () => queue === 'agenda', hasMore: () => false, hasCheckpoint: () => true, resume: () => {{ opens += 1; return true; }}, start: () => {{ throw new Error('must resume'); }}, announce: () => {{}}, }}); const first = launcher.open(); const second = launcher.open(); Promise.resolve().then(() => {{ const before = {{discoveries, opens, same:first === second}}; finishDiscovery(true); return Promise.all([first, second]).then(results => {{ process.stdout.write(JSON.stringify({{before, results, discoveries, opens}})); }}); }}); """ result = subprocess.run(["node", "-e", script], capture_output=True, text=True) assert result.returncode == 0, result.stderr assert json.loads(result.stdout) == { "before": {"discoveries": 1, "opens": 0, "same": True}, "results": ["opened", "opened"], "discoveries": 1, "opens": 1, } def test_agenda_session_launch_preserves_progress_when_discovery_fails_or_queue_changes(): script = f""" const createLauncher = require({json.dumps(str(AGENDA_SESSION_LAUNCHER))}); async function scenario(mode) {{ let queue = 'agenda'; let opens = 0; const messages = []; const launcher = createLauncher({{ selectAgenda: () => {{ queue = 'agenda'; }}, discover: async () => {{ if (mode === 'leave') queue = 'today'; if (mode === 'throw') throw new Error('offline'); return mode !== 'incomplete'; }}, isAgendaSelected: () => queue === 'agenda', hasMore: () => mode === 'incomplete', hasCheckpoint: () => true, resume: () => {{ opens += 1; return true; }}, start: () => {{ opens += 1; return true; }}, announce: message => messages.push(message), }}); return {{result:await launcher.open(), opens, messages}}; }} Promise.all(['incomplete', 'throw', 'leave'].map(scenario)).then(results => process.stdout.write(JSON.stringify(results)) ); """ result = subprocess.run(["node", "-e", script], capture_output=True, text=True) assert result.returncode == 0, result.stderr assert json.loads(result.stdout) == [ {"result": "incomplete", "opens": 0, "messages": [ "Agenda check paused. Retry to check older assigned deadlines." ]}, {"result": "incomplete", "opens": 0, "messages": [ "Agenda check paused. Retry to check older assigned deadlines." ]}, {"result": "cancelled", "opens": 0, "messages": []}, ] def test_protect_today_route_is_an_explicit_agenda_action(): script = f""" const routes = require({json.dumps(str(WORK_ROUTE))}); process.stdout.write(JSON.stringify(routes.parse('#/my-work/agenda/protect-today'))); """ result = subprocess.run( ["node", "-e", script], check=True, capture_output=True, text=True ) assert json.loads(result.stdout) == { "kind": "queue", "filter": "agenda", "action": "protect-today" } CARD_PLANNING = Path(__file__).parents[1] / "frontend" / "card-planning.js" TODAY_WORK = Path(__file__).parents[1] / "frontend" / "today-work.js" WORK_SELECTION = Path(__file__).parents[1] / "frontend" / "work-selection.js" def test_work_selection_selects_matching_items_in_order_with_an_explicit_cap(): script = f""" const createWorkSelection = require({json.dumps(str(WORK_SELECTION))}); const selection = createWorkSelection({{limit:3}}); selection.start(); selection.select({{kind:'issue', repository:'stackchain/api', number:1}}); const result = selection.selectMany([ {{kind:'issue', repository:'stackchain/api', number:1}}, {{kind:'pull', repository:'stackchain/web', number:2}}, {{kind:'issue', repository:'stackchain/app', number:3}}, {{kind:'issue', repository:'stackchain/overflow', number:4}}, ]); process.stdout.write(JSON.stringify({{result, snapshot:selection.snapshot()}})); """ result = subprocess.run( ["node", "-e", script], check=True, capture_output=True, text=True ) output = json.loads(result.stdout) assert output == { "result": {"status": "limit", "added": 2, "count": 3, "limit": 3}, "snapshot": { "active": True, "count": 3, "ids": [ "issue:stackchain/api:1:", "pull:stackchain/web:2:", "issue:stackchain/app:3:", ], }, } def test_work_selection_clears_matches_without_leaving_selection_mode(): script = f""" const createWorkSelection = require({json.dumps(str(WORK_SELECTION))}); const selection = createWorkSelection(); selection.start(); selection.selectMany([ {{kind:'issue', repository:'stackchain/api', number:1}}, {{kind:'pull', repository:'stackchain/web', number:2}}, ]); process.stdout.write(JSON.stringify(selection.clear())); """ result = subprocess.run( ["node", "-e", script], check=True, capture_output=True, text=True ) assert json.loads(result.stdout) == {"active": True, "count": 0, "ids": []} @pytest.mark.anyio async def test_mobile_batch_planning_can_select_and_clear_active_queue_matches(): html = await dashboard() assert 'id="select-matching-work"' in html assert 'id="clear-work-selection"' in html assert "workSelection.selectMany(visible)" in html assert "workSelection.clear()" in html assert "matches selected" in html assert ".selection-scope-actions button { min-height:44px;" in html assert "max-width:100%" in html @pytest.mark.anyio async def test_issue_sheet_formats_due_date_as_a_calendar_day_without_local_timestamp_conversion(): source = await dashboard() assert "formatCalendarDueDate(detail.due_date)" in source assert "new Date(detail.due_date).toLocaleDateString()" not in source def test_queue_finder_matches_repository_number_and_title_without_reordering(): script = f""" const work = require({json.dumps(str(MY_WORK))}); const items = [ {{repository:'stackchain/api', key:'stackchain/api#42', number:42, title:'Retry failed deploy'}}, {{repository:'stackchain/web', key:'stackchain/web#7', number:7, title:'Polish mobile queue'}}, {{repository:'other/repo', key:'other/repo#42', number:42, title:'Unrelated task'}}, ]; process.stdout.write(JSON.stringify({{ repo:work.findQueueItems(items, 'STACKCHAIN/API').map(item => item.key), number:work.findQueueItems(items, '#42').map(item => item.key), title:work.findQueueItems(items, 'mobile queue').map(item => item.key), clear:work.findQueueItems(items, ' ').map(item => item.key), }})); """ result = subprocess.run(["node", "-e", script], capture_output=True, text=True) assert result.returncode == 0, result.stderr assert json.loads(result.stdout) == { "repo": ["stackchain/api#42"], "number": ["stackchain/api#42", "other/repo#42"], "title": ["stackchain/web#7"], "clear": ["stackchain/api#42", "stackchain/web#7", "other/repo#42"], } def test_mobile_agenda_groups_assigned_deadlines_in_local_seven_day_horizon(): script = f""" const work = require({json.dumps(str(MY_WORK))}); const now = new Date('2026-08-12T12:00:00'); const items = [ {{kind:'issue', key:'o/r#5', repository:'o/r', number:5, is_assigned:true, due_date:'2026-08-11T18:00:00'}}, {{kind:'issue', key:'o/r#3', repository:'o/r', number:3, is_assigned:true, due_date:'2026-08-12T17:00:00'}}, {{kind:'issue', key:'a/r#9', repository:'a/r', number:9, is_assigned:true, due_date:'2026-08-13T09:00:00'}}, {{kind:'issue', key:'a/r#2', repository:'a/r', number:2, is_assigned:true, due_date:'2026-08-18T09:00:00'}}, {{kind:'issue', key:'a/r#1', repository:'a/r', number:1, is_assigned:true, due_date:'2026-08-20T09:00:00'}}, {{kind:'issue', key:'a/r#7', repository:'a/r', number:7, is_assigned:false, due_date:'2026-08-13T08:00:00'}}, {{kind:'pull', key:'a/r#8', repository:'a/r', number:8, is_assigned:true, due_date:'2026-08-13T08:00:00'}}, {{kind:'issue', key:'a/r#6', repository:'a/r', number:6, is_assigned:true}}, ]; process.stdout.write(JSON.stringify(work.agendaMyWork(items, now).map(item => [item.key, item.agenda_group]))); """ result = subprocess.run(["node", "-e", script], capture_output=True, text=True) assert result.returncode == 0, result.stderr assert json.loads(result.stdout) == [ ["o/r#5", "Overdue"], ["o/r#3", "Today"], ["a/r#9", "Tomorrow"], ["a/r#2", "Next 7 days"], ] def test_mobile_agenda_sorts_equal_deadlines_by_repository_and_number(): script = f""" const work = require({json.dumps(str(MY_WORK))}); const items = [ {{kind:'issue', key:'z/r#1', repository:'z/r', number:1, is_assigned:true, due_date:'2026-08-14T09:00:00'}}, {{kind:'issue', key:'a/r#10', repository:'a/r', number:10, is_assigned:true, due_date:'2026-08-14T09:00:00'}}, {{kind:'issue', key:'a/r#2', repository:'a/r', number:2, is_assigned:true, due_date:'2026-08-14T09:00:00'}}, ]; process.stdout.write(JSON.stringify(work.agendaMyWork(items, new Date('2026-08-12T12:00:00')).map(item => item.key))); """ result = subprocess.run(["node", "-e", script], capture_output=True, text=True) assert result.returncode == 0, result.stderr assert json.loads(result.stdout) == ["a/r#2", "a/r#10", "z/r#1"] def test_mobile_agenda_runs_as_an_ordered_non_durable_work_session(): script = f""" const buildMyWork = require({json.dumps(str(MY_WORK))}); const values = new Map([['stackchain.today-session.v1', 'preserve-today']]); const checkpoint = {{ read:() => null, save:() => {{ throw new Error('Agenda must not save Today'); }}, clear:() => {{ throw new Error('Agenda must not clear Today'); }}, }}; const active = [ {{kind:'issue',repository:'o/r',number:3,title:'Tomorrow',is_assigned:true,due_date:'2026-08-14'}}, {{kind:'issue',repository:'o/r',number:1,title:'Overdue',is_assigned:true,due_date:'2026-08-12'}}, {{kind:'issue',repository:'o/r',number:2,title:'Today',is_assigned:true,due_date:'2026-08-13'}}, {{kind:'issue',repository:'o/r',number:4,title:'No deadline',is_assigned:true}}, ]; const opened = []; let finished = 0; const session = buildMyWork.createWorkSession({{ getItems:() => buildMyWork.agendaMyWork(active, new Date('2026-08-13T12:00:00')), getFilter:() => 'all', checkpoint, checkpointEnabled:() => false, onOpen:item => opened.push(item.title), onProgress:() => {{}}, onFinish:() => {{ finished += 1; }}, }}); const started = session.start(); session.next(); session.next(); const completed = session.next(); process.stdout.write(JSON.stringify({{ started, completed, opened, finished, active:session.active(), today:values.get('stackchain.today-session.v1'), }})); """ result = subprocess.run(["node", "-e", script], capture_output=True, text=True) assert result.returncode == 0, result.stderr assert json.loads(result.stdout) == { "started": True, "completed": False, "opened": ["Overdue", "Today", "Tomorrow"], "finished": 1, "active": False, "today": "preserve-today", } def test_mobile_agenda_checkpoint_is_account_bound_resumable_and_separate_from_today(): script = f""" const buildMyWork = require({json.dumps(str(MY_WORK))}); const values = new Map([['stackchain.today-session.v1', 'preserve-today']]); const storage = {{ getItem:key => values.get(key) || null, setItem:(key, value) => values.set(key, value), removeItem:key => values.delete(key), }}; let login = 'timmy'; const agendaCheckpoint = buildMyWork.createWorkSessionCheckpoint({{ storage, getLogin:() => login, key:'stackchain.agenda-session.v1', }}); const todayCheckpoint = buildMyWork.createWorkSessionCheckpoint({{ storage, getLogin:() => login, key:'stackchain.today-session.v1', }}); let mode = 'agenda'; let items = [ {{kind:'issue',repository:'o/r',number:1,title:'Overdue'}}, {{kind:'issue',repository:'o/r',number:2,title:'Today'}}, {{kind:'issue',repository:'o/r',number:3,title:'Tomorrow'}}, ]; const opened = []; const makeSession = () => buildMyWork.createWorkSession({{ getItems:() => items, getFilter:() => 'all', checkpoint:() => mode === 'agenda' ? agendaCheckpoint : todayCheckpoint, checkpointEnabled:() => true, checkpointedEnabled:() => mode === 'today', onOpen:item => opened.push(item.title), onProgress:() => {{}}, onFinish:() => {{}}, }}); const first = makeSession(); first.start(); first.next(); const saved = JSON.parse(values.get('stackchain.agenda-session.v1')); const todayWhileRunning = first.checkpointed(); login = 'alexander'; const hiddenFromOtherAccount = makeSession().resumable(); login = 'timmy'; items = items.filter(item => item.number !== 2); const resumedSession = makeSession(); const resumed = resumedSession.resume(); resumedSession.end(); process.stdout.write(JSON.stringify({{ saved, todayWhileRunning, hiddenFromOtherAccount, resumed, opened, agendaCleared:!values.has('stackchain.agenda-session.v1'), today:values.get('stackchain.today-session.v1'), }})); """ result = subprocess.run(["node", "-e", script], capture_output=True, text=True) assert result.returncode == 0, result.stderr state = json.loads(result.stdout) assert state["saved"] == { "version": 1, "login": "timmy", "identity": "issue:o/r:2:", "index": 1, } assert state["todayWhileRunning"] is False assert state["hiddenFromOtherAccount"] is False assert state["resumed"] is True assert state["opened"] == ["Overdue", "Today", "Tomorrow"] assert state["agendaCleared"] is True assert state["today"] == "preserve-today" @pytest.mark.anyio async def test_mobile_agenda_launcher_starts_or_resumes_the_durable_agenda_session(): html = await dashboard() assert '' in html assert "openAgenda: openAgendaSession" in html assert "const agendaSessionLauncher = createAgendaSessionLauncher({" in html assert "discover: completeAgendaIssues" in html assert "hasMore: () => Boolean(workPagination.issue?.has_more)" in html assert "key: 'stackchain.agenda-session.v1'" in html assert "checkpoint: () => selectedWorkFilter === 'agenda' ? agendaSessionCheckpoint : sessionCheckpoint" in html assert "checkpointEnabled: () => ['today', 'agenda'].includes(selectedWorkFilter)" in html assert "checkpointedEnabled: () => selectedWorkFilter === 'today'" in html assert "agendaSessionCheckpoint.read() ? 'Resume Agenda' : 'Start Agenda'" in html assert "window.location.hash = '#/my-work/agenda'" in html @pytest.mark.anyio async def test_mobile_agenda_launcher_starts_the_rendered_queue_and_finishes_in_agenda(): html = await dashboard() assert "selectedWorkFilter === 'agenda' ? agendaMyWork(activeMyWork)" in html assert "selectedWorkFilter === 'agenda' ? 'all'" in html assert "agendaSessionCheckpoint.read() ? 'Resume Agenda' : 'Start Agenda'" in html assert "const sessionItems = workSession.items()" in html assert "qs('#my-work-action-status').textContent = 'Agenda complete.'" in html assert "checkpointEnabled: () => ['today', 'agenda'].includes(selectedWorkFilter)" in html @pytest.mark.parametrize("timezone_name", ["Asia/Tokyo", "America/Los_Angeles"]) def test_mobile_agenda_preserves_the_gitea_calendar_day_in_every_timezone(timezone_name): script = f""" const work = require({json.dumps(str(MY_WORK))}); const now = new Date(2026, 7, 13, 12, 0, 0); const built = work({{ user:{{login:'timmy'}}, notifications:[], pull_requests:[], issues:[{{number:1,title:'Ship',repository:'o/r',labels:[],assignees:['timmy'],due_date:'2026-08-13T23:59:59Z'}}], }}, now); process.stdout.write(JSON.stringify({{ dueLabel:built[0].due_label, agenda:work.agendaMyWork(built, now).map(item => [item.key, item.agenda_group]), }})); """ environment = {**os.environ, "TZ": timezone_name} result = subprocess.run( ["node", "-e", script], capture_output=True, text=True, env=environment ) assert result.returncode == 0, result.stderr assert json.loads(result.stdout) == { "dueLabel": "Due today", "agenda": [["o/r#1", "Today"]], } def test_agenda_pager_loads_every_issue_page_single_flight_and_retries_failed_page(): script = f""" const work = require({json.dumps(str(MY_WORK))}); const calls = []; let failPage = 3; let items = [{{id:1, title:'first'}}]; let pagination = {{issue:{{page:1,total:4,has_more:true}}}}; const pager = work.createWorkPager({{ load: async (stream, page) => {{ calls.push(page); await new Promise(resolve => setTimeout(resolve, 5)); if (page === failPage) throw new Error('offline'); return {{page,total:4,has_more:page < 4,items:[{{id:page,title:'page '+page}}]}}; }}, onItems: (_stream, next) => {{ items = next; }}, onPagination: next => {{ pagination = next; }}, onStatus: () => {{}}, }}); pager.reset(pagination); async function run() {{ const first = pager.loadAll('issue', () => items); const duplicate = pager.loadAll('issue', () => items); const failed = await first; const samePromise = first === duplicate; failPage = 0; const retried = await pager.loadAll('issue', () => items); process.stdout.write(JSON.stringify({{ failed, samePromise, retried, calls, ids:items.map(item => item.id), pagination, }})); }} run(); """ result = subprocess.run(["node", "-e", script], capture_output=True, text=True) assert result.returncode == 0, result.stderr assert json.loads(result.stdout) == { "failed": False, "samePromise": True, "retried": True, "calls": [2, 3, 3, 4], "ids": [1, 2, 3, 4], "pagination": {"issue": {"page": 4, "total": 4, "has_more": False}}, } @pytest.mark.anyio async def test_agenda_activation_checks_all_issue_pages_before_showing_empty_state(): html = await dashboard() assert "workPager.loadAll('issue'" in html assert "Checking all assigned deadlines…" in html assert "Agenda check paused. Retry to check older assigned deadlines." in html assert "selectedWorkFilter === 'agenda' && workPagination.issue?.has_more" in html @pytest.mark.anyio async def test_mobile_queue_finder_is_labeled_thumb_safe_and_offers_older_search(): html = await dashboard() assert '