import json import subprocess from pathlib import Path MODULE = Path(__file__).parents[1] / "frontend" / "photo-draft-inbox.js" INDEX = Path(__file__).parents[1] / "frontend" / "index.html" DASHBOARD = Path(__file__).parents[1] / "frontend" / "dashboard.js" WORKER = Path(__file__).parents[1] / "frontend" / "service-worker.js" BUNDLE = Path(__file__).parents[1] / "src" / "frontend_bundle.py" def run_node(script: str): completed = subprocess.run(["node", "-e", script], check=True, capture_output=True, text=True) return json.loads(completed.stdout) def test_photo_draft_inbox_combines_stores_without_duplicate_target_cards_and_discards_exact_bundle(): script = f""" const createPhotoDraftInbox = require({json.dumps(str(MODULE))}); const removed=[]; const conversation={{ list:async()=>[ {{id:'conversation-issue',kind:'photo-reply',photo_store:'conversation',title:'o/r#7',updated_at:30,route:{{kind:'issue',repository:'o/r',number:7}}}}, {{id:'conversation-update',kind:'photo-reply',photo_store:'conversation',title:'Update #8',updated_at:20,route:{{kind:'update',notification_id:8}}}}, ], remove:async target=>removed.push(['conversation',target]), }}; const search={{ list:async()=>[ {{id:'search-pull',kind:'photo-reply',photo_store:'search',title:'o/r#9',updated_at:40,route:{{kind:'search',target_kind:'pull',repository:'o/r',number:9}}}}, ], remove:async target=>removed.push(['search',target]), }}; const inbox=createPhotoDraftInbox({{conversation,search}}); (async()=>{{ const drafts=await inbox.refresh([{{kind:'issue-comment',route:{{kind:'issue',repository:'o/r',number:7}}}}]); const description=inbox.description({{kind:'photo-reply',photo_count:2}}); const searchTarget=inbox.searchTarget(drafts[0]); await inbox.discard(drafts[0]); process.stdout.write(JSON.stringify({{drafts,description,searchTarget,remaining:inbox.list(),removed}})); }})().catch(error=>{{console.error(error);process.exit(1);}}); """ assert run_node(script) == { "drafts": [ { "id": "search-pull", "kind": "photo-reply", "photo_store": "search", "title": "o/r#9", "updated_at": 40, "route": {"kind": "search", "target_kind": "pull", "repository": "o/r", "number": 9}, }, { "id": "conversation-update", "kind": "photo-reply", "photo_store": "conversation", "title": "Update #8", "updated_at": 20, "route": {"kind": "update", "notification_id": 8}, }, ], "description": "2 saved photos", "searchTarget": {"kind": "pull", "repository": "o/r", "number": 9, "title": "o/r#9"}, "remaining": [ { "id": "conversation-update", "kind": "photo-reply", "photo_store": "conversation", "title": "Update #8", "updated_at": 20, "route": {"kind": "update", "notification_id": 8}, } ], "removed": [["search", {"kind": "pull", "repository": "o/r", "number": 9}]], } def test_photo_draft_inbox_keeps_available_store_when_other_inventory_fails(): script = f""" const createPhotoDraftInbox = require({json.dumps(str(MODULE))}); const inbox=createPhotoDraftInbox({{ conversation:{{list:async()=>{{throw new Error('IndexedDB unavailable')}}}}, search:{{list:async()=>[{{id:'safe',route:{{kind:'search',target_kind:'issue',repository:'o/r',number:2}},updated_at:1}}]}}, }}); (async()=>process.stdout.write(JSON.stringify(await inbox.refresh([]))))().catch(error=>{{console.error(error);process.exit(1);}}); """ assert run_node(script) == [ {"id": "safe", "route": {"kind": "search", "target_kind": "issue", "repository": "o/r", "number": 2}, "updated_at": 1} ] def test_mobile_my_work_discovers_reopens_and_discards_photo_only_reply_drafts(): index = INDEX.read_text() dashboard = DASHBOARD.read_text() worker = WORKER.read_text() bundle = BUNDLE.read_text() assert '' in index assert "BASE + 'static/photo-draft-inbox.js'" in worker assert '"static/photo-draft-inbox.js"' in bundle assert "const photoDraftInbox = createPhotoDraftInbox({" in dashboard assert "draftInbox.list().concat(photoDraftInbox.list(), unfiled)" in dashboard assert "await photoDraftInbox.refresh(draftInbox.list())" in dashboard assert "refreshMyWorkView({ reconcileSession:false, refreshFirstTask:false })" in dashboard assert "void refreshPhotoDraftInbox();" in dashboard assert "onChange:() => { void refreshPhotoDraftInbox(); }" in dashboard assert "await searchReplyDraftStore.save(target, attachments);\n void refreshPhotoDraftInbox();" in dashboard assert "photoDraftInbox.description(item)" in dashboard assert "photoDraftInbox.searchTarget(item)" in dashboard assert "await photoDraftInbox.discard(item)" in dashboard