112 lines
4.3 KiB
Python
112 lines
4.3 KiB
Python
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 '<script src="static/unfiled-captures.js"></script>' 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
|