stackchain-dashboard/tests/test_issue_filing_review.py
timmy 59a2e4f616
All checks were successful
CI / lint (pull_request) Successful in 1m42s
CI / build-release (pull_request) Successful in 5s
CI / release-candidate (pull_request) Has been skipped
feat: review mobile issue payload before filing (Closes #833)
2026-08-14 15:36:54 +00:00

145 lines
5.8 KiB
Python

import json
import subprocess
from pathlib import Path
MODULE = Path(__file__).parents[1] / "frontend" / "issue-filing-review.js"
def run_node(script: str) -> dict:
completed = subprocess.run(
["node", "-e", script], capture_output=True, text=True, check=True
)
return json.loads(completed.stdout)
def test_review_requires_one_confirmation_and_preserves_the_reviewed_payload():
script = f"""
const createReview = require({json.dumps(str(MODULE))});
function target() {{
const listeners = {{}};
return {{
disabled:false, hidden:true, textContent:'', children:[],
addEventListener:(name, fn)=>listeners[name]=fn,
dispatch:(name, event={{}})=>listeners[name]?.({{preventDefault(){{}}, ...event}}),
focus(){{this.focused=true;}}, replaceChildren(...items){{this.children=items;}},
setAttribute(){{}}, appendChild(item){{this.children.push(item);}},
}};
}}
const sheet=target(), confirm=target(), back=target(), trigger=target(), evidence=target();
const documentRef={{
createElement:tag=>({{tag, textContent:'', children:[], appendChild(item){{this.children.push(item);}}}}),
addEventListener:()=>{{}},
}};
const admitted=[];
const review=createReview({{
sheet, confirmButton:confirm, backButton:back, evidenceList:evidence,
repository:target(), intent:target(), title:target(), body:target(), metadata:target(),
document:documentRef,
onConfirm:async payload=>{{ admitted.push(payload); return {{ok:true}}; }},
}});
const payload={{
draft:{{repository:'stackchain/dashboard',title:'Ship it',body:'Full note',labels:['P1'],
milestone:{{title:'Sprint'}},dueDate:'2026-08-20',attachments:[
{{filename:'first.png',note:'Before'}},{{filename:'second.png',note:'After'}}
]}},
intent:'create-and-start',
}};
review.open(payload, trigger);
payload.draft.title='Mutated outside';
confirm.dispatch('click');
confirm.dispatch('click');
setImmediate(()=>process.stdout.write(JSON.stringify({{
admissions:admitted.length,
reviewedTitle:admitted[0].draft.title,
evidence:evidence.children.map(item=>item.textContent),
hidden:sheet.hidden,
triggerFocused:trigger.focused || false,
}})));
"""
result = run_node(script)
assert result == {
"admissions": 1,
"reviewedTitle": "Ship it",
"evidence": ["1. first.png — Before", "2. second.png — After"],
"hidden": True,
"triggerFocused": True,
}
def test_escape_returns_to_the_unchanged_issue_form():
script = f"""
const createReview = require({json.dumps(str(MODULE))});
function target() {{
const listeners={{}};
return {{hidden:true,disabled:false,textContent:'',children:[],
addEventListener:(name,fn)=>listeners[name]=fn,
replaceChildren(...items){{this.children=items;}},
focus(){{this.focused=true;}}, dispatch:(name,event)=>listeners[name]?.(event)}};
}}
const listeners={{}}, documentRef={{
createElement:()=>({{textContent:''}}),
addEventListener:(name,fn)=>listeners[name]=fn,
dispatch:(name,event)=>listeners[name]?.(event),
}};
const sheet=target(), trigger=target();
const review=createReview({{
sheet,confirmButton:target(),backButton:target(),evidenceList:target(),
repository:target(),intent:target(),title:target(),body:target(),metadata:target(),
document:documentRef,onConfirm:async()=>{{}},
}});
const payload={{draft:{{repository:'o/r',title:'Original',body:'Note'}},intent:'create-and-assign'}};
review.open(payload,trigger);
documentRef.dispatch('keydown',{{key:'Escape',preventDefault(){{this.prevented=true;}}}});
process.stdout.write(JSON.stringify({{hidden:sheet.hidden,focused:trigger.focused||false,title:payload.draft.title}}));
"""
assert run_node(script) == {"hidden": True, "focused": True, "title": "Original"}
def test_create_actions_review_the_complete_payload_before_durable_admission():
root = Path(__file__).parents[1]
html = (root / "frontend" / "index.html").read_text()
dashboard = (root / "frontend" / "dashboard.js").read_text()
css = (root / "frontend" / "dashboard.css").read_text()
bundle = (root / "src" / "frontend_bundle.py").read_text()
assert 'id="issue-filing-review" role="dialog" aria-modal="true"' in html
assert 'id="issue-filing-review-repository"' in html
assert 'id="issue-filing-review-intent"' in html
assert 'id="issue-filing-review-title"' in html
assert 'id="issue-filing-review-body"' in html
assert 'id="issue-filing-review-evidence"' in html
assert 'id="back-to-issue-edit"' in html
assert 'id="confirm-issue-filing"' in html
assert '<script src="static/issue-filing-review.js"></script>' in html
submit_handler = dashboard.split(
"qs('#create-issue-form').addEventListener('submit'", 1
)[1].split("qs('#close-issue-sheet').addEventListener", 1)[0]
assert "filingReview.open" in submit_handler
assert "issueOutbox.enqueueDurably" not in submit_handler
assert "async function admitReviewedIssue" in dashboard
assert "issueOutbox.enqueueDurably" in dashboard.split(
"async function admitReviewedIssue", 1
)[1].split("qs('#create-issue-form').addEventListener", 1)[0]
assert ".issue-filing-review" in css
assert "max-height:100dvh" in css
assert "overflow-x:hidden" in css
assert "env(safe-area-inset-bottom)" in css
assert '"static/issue-filing-review.js"' in bundle
def test_duplicate_acknowledgement_preserves_the_selected_filing_intent():
dashboard = (Path(__file__).parents[1] / "frontend" / "dashboard.js").read_text()
assert "let pendingIssueFilingIntent = 'create-and-assign';" in dashboard
assert "if (event.submitter) pendingIssueFilingIntent" in dashboard
assert "const intent = pendingIssueFilingIntent;" in dashboard
assert "pendingIssueFilingIntent = 'create-and-assign';" in dashboard.split(
"async function admitReviewedIssue", 1
)[1].split("qs('#create-issue-form').addEventListener", 1)[0]