import json import subprocess from pathlib import Path import pytest from tests.dashboard_bundle import dashboard UNFILED = Path(__file__).parents[1] / "frontend" / "unfiled-captures.js" def run_node(script: str): result = subprocess.run(["node", "-e", script], check=True, capture_output=True, text=True) return json.loads(result.stdout) def test_unfiled_captures_keep_twenty_newest_account_bound_notes(): script = f""" const createUnfiledCaptures = require({json.dumps(str(UNFILED))}); const values = new Map(); const storage = {{ getItem:key => values.get(key) || null, setItem:(key,value) => values.set(key,value), removeItem:key => values.delete(key), }}; let sequence = 0; const captures = createUnfiledCaptures({{ storage, getCaptureLogin:()=>'timmy', getCurrentLogin:()=>'', createId:()=>String(++sequence), now:()=>1000 + sequence, }}); for (let index = 1; index <= 22; index += 1) {{ captures.save({{title:'Note ' + index, body:'Context ' + index}}); }} const offline = captures.list(); const restored = createUnfiledCaptures({{ storage, getCaptureLogin:()=>'timmy', getCurrentLogin:()=>'timmy' }}).list(); process.stdout.write(JSON.stringify({{offline, restored}})); """ output = run_node(script) assert len(output["offline"]) == 20 assert output["offline"][0]["title"] == "Note 22" assert output["offline"][-1]["title"] == "Note 3" assert all(item["quarantined"] for item in output["offline"]) assert all(not item["quarantined"] for item in output["restored"]) assert output["restored"][0]["ownerLogin"] == "timmy" def test_unfiled_capture_resume_requires_matching_confirmed_account_and_removes_only_selected_note(): script = f""" const createUnfiledCaptures = require({json.dumps(str(UNFILED))}); const values = new Map(); const storage = {{getItem:k=>values.get(k)||null,setItem:(k,v)=>values.set(k,v),removeItem:k=>values.delete(k)}}; let id = 0; const captures = createUnfiledCaptures({{ storage, getCaptureLogin:()=>'timmy', getCurrentLogin:()=>'timmy', createId:()=>String(++id), now:()=>id }}); const first = captures.save({{title:'First',body:'One'}}); const second = captures.save({{title:'Second',body:'Two'}}); let mismatch = ''; try {{ captures.resume(first.id, 'alexander'); }} catch (error) {{ mismatch = error.message; }} const resumed = captures.resume(first.id, 'timmy'); process.stdout.write(JSON.stringify({{mismatch,resumed,remaining:captures.list(),second}})); """ output = run_node(script) assert output["mismatch"] == "Reconnect with the account that saved this capture." assert output["resumed"] == {"repository": "", "title": "First", "body": "One", "labelIds": []} assert [item["id"] for item in output["remaining"]] == [output["second"]["id"]] def test_unfiled_capture_rejects_empty_or_identityless_records_without_writing(): script = f""" const createUnfiledCaptures = require({json.dumps(str(UNFILED))}); const values = new Map(); const storage = {{getItem:k=>values.get(k)||null,setItem:(k,v)=>values.set(k,v)}}; const captures = createUnfiledCaptures({{storage,getCaptureLogin:()=>''}}); const errors = []; for (const note of [{{title:'',body:'context'}},{{title:'Work',body:'context'}}]) {{ try {{ captures.save(note); }} catch (error) {{ errors.push(error.message); }} }} process.stdout.write(JSON.stringify({{errors,size:values.size}})); """ output = run_node(script) assert output == { "errors": ["Add a title before saving.", "Offline identity is unavailable. Reconnect once before saving private work."], "size": 0, } @pytest.mark.anyio async def test_mobile_composer_exposes_cold_offline_save_and_account_safe_resume_flow(): html = await dashboard() assert '' in html assert 'id="save-unfiled-issue"' in html assert 'Save for filing' in html assert "createUnfiledCaptures({" in html assert "getCaptureLogin: () => String(lastContextSnapshot?.user?.login || '').trim()" in html assert "unfiledCaptures.save(captureDraft)" in html assert "unfiledCaptures.resume(item.capture_id, activeFlushLogin)" in html assert "issueCapture.saveDraft(resumed)" in html assert "item.kind === 'unfiled-issue'" in html assert '.create-issue-actions button { min-height:44px;' in html assert '@media(max-width:320px)' in html