feat: carry mobile review feedback into Gitea (#129)
This commit is contained in:
1 parent
bd0ae619db
commit
331eb99c1e
3 files changed
+266
No files matched your search
@@ -260,6 +260,89 @@ process.stdout.write(JSON.stringify(changed));
|
||||
}
|
||||
|
||||
|
||||
def test_review_feedback_draft_restores_notes_summary_and_decision_for_same_head():
|
||||
script = f"""
|
||||
const reviewSheet = require({json.dumps(str(REVIEW_SHEET))});
|
||||
const values = new Map();
|
||||
const storage = {{
|
||||
getItem(key) {{ return values.has(key) ? values.get(key) : null; }},
|
||||
setItem(key, value) {{ values.set(key, value); }}
|
||||
}};
|
||||
const options = {{
|
||||
storage, repository: 'stackchain/api', number: 7, headSha: 'abc123',
|
||||
files: [{{filename:'src/a.py'}}, {{filename:'src/b.py'}}]
|
||||
}};
|
||||
const first = reviewSheet.createDraft(options);
|
||||
first.setNote('src/a.py', 'Handle the empty state.');
|
||||
first.setSummary('One blocker remains.');
|
||||
first.setDecision('request_changes');
|
||||
const restored = reviewSheet.createDraft(options).snapshot();
|
||||
process.stdout.write(JSON.stringify(restored));
|
||||
"""
|
||||
result = subprocess.run(
|
||||
["node", "-e", script], check=True, capture_output=True, text=True
|
||||
)
|
||||
|
||||
assert json.loads(result.stdout) == {
|
||||
"notes": {"src/a.py": "Handle the empty state."},
|
||||
"summary": "One blocker remains.",
|
||||
"decision": "request_changes",
|
||||
}
|
||||
|
||||
|
||||
def test_review_feedback_formats_non_empty_file_notes_in_changed_file_order():
|
||||
script = f"""
|
||||
const reviewSheet = require({json.dumps(str(REVIEW_SHEET))});
|
||||
const markdown = reviewSheet.formatFeedback({{
|
||||
decision: 'request_changes', summary: 'One blocker remains.',
|
||||
notes: {{'src/b.py':'Second note', 'src/a.py':'First note', 'README.md':''}}
|
||||
}}, [{{filename:'src/a.py'}}, {{filename:'README.md'}}, {{filename:'src/b.py'}}]);
|
||||
process.stdout.write(markdown);
|
||||
"""
|
||||
result = subprocess.run(
|
||||
["node", "-e", script], check=True, capture_output=True, text=True
|
||||
)
|
||||
|
||||
assert result.stdout == (
|
||||
"## Intended decision\nRequest changes\n\n"
|
||||
"## Summary\nOne blocker remains.\n\n"
|
||||
"## File notes\n### `src/a.py`\nFirst note\n\n"
|
||||
"### `src/b.py`\nSecond note"
|
||||
)
|
||||
|
||||
|
||||
def test_review_handoff_opens_gitea_only_after_copy_and_preserves_failed_copy():
|
||||
script = f"""
|
||||
const reviewSheet = require({json.dumps(str(REVIEW_SHEET))});
|
||||
const events = [];
|
||||
const success = reviewSheet.copyAndContinue({{
|
||||
text: 'feedback', url: 'https://forge.example/pulls/7',
|
||||
copy: async text => events.push('copy:' + text),
|
||||
open: url => events.push('open:' + url),
|
||||
fallback: text => events.push('fallback:' + text),
|
||||
}});
|
||||
const failedEvents = [];
|
||||
const failure = reviewSheet.copyAndContinue({{
|
||||
text: 'keep me', url: 'https://forge.example/pulls/8',
|
||||
copy: async () => {{ throw new Error('denied'); }},
|
||||
open: url => failedEvents.push('open:' + url),
|
||||
fallback: text => failedEvents.push('fallback:' + text),
|
||||
}});
|
||||
Promise.all([success, failure]).then(results => process.stdout.write(JSON.stringify({{
|
||||
events, failedEvents, results
|
||||
}})));
|
||||
"""
|
||||
result = subprocess.run(
|
||||
["node", "-e", script], check=True, capture_output=True, text=True
|
||||
)
|
||||
|
||||
assert json.loads(result.stdout) == {
|
||||
"events": ["copy:feedback", "open:https://forge.example/pulls/7"],
|
||||
"failedEvents": ["fallback:keep me"],
|
||||
"results": [True, False],
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_review_requests_open_an_accessible_mobile_detail_sheet():
|
||||
html = await dashboard()
|
||||
@@ -295,6 +378,28 @@ async def test_review_sheet_loads_details_and_preserves_safe_gitea_handoff():
|
||||
assert "reviewController.submit" not in html
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_mobile_review_sheet_captures_and_safely_hands_off_feedback():
|
||||
html = await dashboard()
|
||||
review_script = REVIEW_SHEET.read_text()
|
||||
|
||||
assert 'class="review-note"' in review_script
|
||||
assert 'id="review-decision"' in html
|
||||
assert 'id="review-summary"' in html
|
||||
assert 'id="copy-review-feedback"' in html
|
||||
assert 'id="review-copy-fallback"' in html
|
||||
assert '.review-note' in html and 'min-height:88px' in html
|
||||
assert '.review-handoff' in html and 'position:sticky' in html
|
||||
assert "createReviewController.createDraft" in html
|
||||
assert "draft.setNote" in html
|
||||
assert "draft?.setSummary" in html
|
||||
assert "draft?.setDecision" in html
|
||||
assert "createReviewController.formatFeedback" in html
|
||||
assert "createReviewController.copyAndContinue" in html
|
||||
assert "navigator.clipboard.writeText" in html
|
||||
assert "window.open(url, '_blank', 'noopener,noreferrer')" in html
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_mobile_review_failure_offers_an_in_place_retry_for_the_same_item():
|
||||
html = await dashboard()
|
||||
|
||||
Reference in new issue
Block a user