fix: preserve screenshots while editing queued captures (Closes #475)
This commit is contained in:
1 parent
535658185a
commit
d161cf41c0
7 files changed
+126
-9
No files matched your search
@@ -383,6 +383,31 @@ const transaction=work=>{{const run=tail.then(()=>work({{
|
||||
assert output[1]["attachment"]["data"] == "second-image-bytes"
|
||||
|
||||
|
||||
def test_issue_sync_store_hydrates_one_capture_by_key_without_scanning_all_records():
|
||||
script = f"""
|
||||
const createBackgroundIssueSync = require({json.dumps(str(SYNC))});
|
||||
const records=new Map([
|
||||
['first',{{id:'first',attachment:{{filename:'one.png',contentType:'image/png',data:'first-bytes'}}}}],
|
||||
['second',{{id:'second',attachment:{{filename:'two.png',contentType:'image/png',data:'second-bytes'}}}}],
|
||||
]);
|
||||
const calls=[];
|
||||
const transaction=work=>work({{
|
||||
get:async id=>{{calls.push(['get',id]);return records.get(id);}},
|
||||
getAll:async()=>{{calls.push(['getAll']);return [...records.values()];}},
|
||||
put:async()=>{{}},delete:async()=>{{}},
|
||||
}});
|
||||
(async()=>{{
|
||||
const store=createBackgroundIssueSync.createIssueSyncStore({{transaction}});
|
||||
const hydrated=await store.get('second');
|
||||
process.stdout.write(JSON.stringify({{hydrated,calls}}));
|
||||
}})();
|
||||
"""
|
||||
output = run_node(script)
|
||||
|
||||
assert output["hydrated"]["attachment"]["data"] == "second-bytes"
|
||||
assert output["calls"] == [["get", "second"]]
|
||||
|
||||
|
||||
def test_stale_foreground_upsert_preserves_confirmed_attachment_delivery_stages():
|
||||
script = f"""
|
||||
const createBackgroundIssueSync=require({json.dumps(str(SYNC))});
|
||||
|
||||
@@ -119,6 +119,24 @@ controller.restore({{filename:'saved.png',contentType:'image/png',data:'iVBORw0K
|
||||
assert output["removed"] is None
|
||||
|
||||
|
||||
def test_metadata_only_attachment_cannot_render_as_an_empty_screenshot():
|
||||
script = f"""
|
||||
const attachment=require({json.dumps(str(ATTACHMENT))});
|
||||
const controller=attachment.create({{readDataUrl:async()=>'',upload:async()=>{{}}}});
|
||||
try {{
|
||||
controller.restore({{filename:'saved.png',contentType:'image/png',stored:true}});
|
||||
process.stdout.write(JSON.stringify({{restored:true,state:controller.state()}}));
|
||||
}} catch (error) {{
|
||||
process.stdout.write(JSON.stringify({{restored:false,message:error.message,state:controller.state()}}));
|
||||
}}
|
||||
"""
|
||||
output = json.loads(run_node(script))
|
||||
|
||||
assert output["restored"] is False
|
||||
assert "saved screenshot" in output["message"].lower()
|
||||
assert output["state"] is None
|
||||
|
||||
|
||||
def test_issue_composer_renders_thumb_reachable_screenshot_preview():
|
||||
html = INDEX.read_text()
|
||||
css = CSS.read_text()
|
||||
@@ -145,12 +163,29 @@ def test_new_issue_sheet_captures_screenshot_into_durable_outbox():
|
||||
assert 'id="remove-create-issue-attachment"' in html
|
||||
assert "const createIssueAttachmentController = issueAttachment.mount({" in source
|
||||
assert "attachment: await createIssueAttachmentController.serialize()" in source
|
||||
assert "createIssueAttachmentController.restore(queued.attachment);" in source
|
||||
assert "createIssueAttachmentController.restore(hydrated.attachment);" in source
|
||||
assert "createIssueAttachmentController.clear();" in source
|
||||
assert ".create-issue-attachment" in css
|
||||
assert "overflow-x:hidden" in css
|
||||
|
||||
|
||||
def test_queued_screenshot_is_hydrated_before_the_issue_editor_opens_and_failure_is_retryable():
|
||||
source = DASHBOARD.read_text()
|
||||
handler = re.search(
|
||||
r"list\.querySelectorAll\('\.draft-edit'\).*?addEventListener\('click', async \(\) => \{(?P<body>.*?)\n \}\);",
|
||||
source,
|
||||
re.DOTALL,
|
||||
)
|
||||
assert handler is not None
|
||||
body = handler.group("body")
|
||||
assert "await issueOutbox.hydrateForEdit(queued.id)" in body
|
||||
assert body.index("await issueOutbox.hydrateForEdit(queued.id)") < body.index(
|
||||
"openCreateIssueSheet()"
|
||||
)
|
||||
assert "catch (error)" in body
|
||||
assert "saved screenshot" in body.lower()
|
||||
|
||||
|
||||
def test_attachment_view_keeps_invalid_draft_and_removes_preview():
|
||||
script = f"""
|
||||
const attachment = require({json.dumps(str(ATTACHMENT))});
|
||||
|
||||
@@ -94,6 +94,31 @@ process.stdout.write(JSON.stringify(createIssueOutbox({{storage}}).list()[0]));
|
||||
}
|
||||
|
||||
|
||||
def test_issue_outbox_hydrates_a_metadata_only_screenshot_for_edit_without_copying_bytes_to_localstorage():
|
||||
script = f"""
|
||||
const createIssueOutbox = require({json.dumps(str(OUTBOX))});
|
||||
const values = new Map();
|
||||
const storage = {{getItem:k=>values.get(k)||null,setItem:(k,v)=>values.set(k,v)}};
|
||||
values.set('stackchain.issue-outbox.v1', JSON.stringify({{version:3,items:[{{
|
||||
id:'capture-1',operationId:'capture-1',repository:'o/r',title:'Before',body:'',
|
||||
ownerLogin:'timmy',status:'queued',attachment:{{filename:'phone.png',contentType:'image/png',stored:true}},
|
||||
}}]}}));
|
||||
const gets=[];
|
||||
const outbox=createIssueOutbox({{storage,backgroundSync:{{
|
||||
get:async id=>{{gets.push(id);return {{id,operationId:'capture-1',attachment:{{filename:'phone.png',contentType:'image/png',data:'durable-image-bytes'}}}};}},
|
||||
}}}});
|
||||
(async()=>{{
|
||||
const hydrated=await outbox.hydrateForEdit('capture-1');
|
||||
process.stdout.write(JSON.stringify({{hydrated,gets,stored:values.get('stackchain.issue-outbox.v1')}}));
|
||||
}})();
|
||||
"""
|
||||
output = run_node(script)
|
||||
|
||||
assert output["hydrated"]["attachment"]["data"] == "durable-image-bytes"
|
||||
assert output["gets"] == ["capture-1"]
|
||||
assert "durable-image-bytes" not in output["stored"]
|
||||
|
||||
|
||||
def test_issue_outbox_persists_create_and_start_intent_and_returns_it_with_confirmation():
|
||||
script = f"""
|
||||
const createIssueOutbox = require({json.dumps(str(OUTBOX))});
|
||||
|
||||
Reference in new issue
Block a user