from pathlib import Path import pytest ROOT = Path(__file__).parents[2] @pytest.mark.parametrize("viewport", [(320, 568), (390, 844)]) def test_mobile_author_previews_and_applies_review_suggestion(viewport): playwright = pytest.importorskip("playwright.sync_api") html = (ROOT / "frontend" / "index.html").read_text() detail = { "state": "open", "merged": False, "head_sha": "abc1234", "capabilities": {"authored": True}, "reviewers": [{ "review_id": 42, "login": "sam", "status": "changes_requested", "head_sha": "abc1234", "blocking": True, "feedback_state": "loaded", "comments": [{ "id": 99, "path": "src/api.py", "line": 2, "body": "Return the typed empty result.\n```suggestion\n return empty_result()\n```", "suggestion": {"line": 2, "content": " return empty_result()"}, }, { "id": 100, "path": "src/api.py", "line": 3, "body": "Keep the result typed.", }], }], "files": [{"filename": "src/api.py", "status": "modified", "diff_available": True, "diff_lines": ["@@ -2 +2 @@", "+ return None"]}], } with playwright.sync_playwright() as runtime: try: browser = runtime.chromium.launch(headless=True) except Exception as error: pytest.skip(f"Chromium is not installed: {error}") page = browser.new_page(viewport={"width": viewport[0], "height": viewport[1]}) page.set_content(html, wait_until="domcontentloaded") page.add_style_tag(path=ROOT / "frontend" / "dashboard.css") page.add_script_tag(path=ROOT / "frontend" / "pull-sheet.js") page.evaluate( """detail => { globalThis.confirm = () => true; globalThis.suggestionCalls = []; globalThis.suggestionItem = {repository:'stackchain/api', number:7, key:'stackchain/api#7'}; globalThis.suggestionDetail = detail; globalThis.suggestionController = createPullSheet({fetchJson: async (path, options = {}) => { suggestionCalls.push({path, options}); if ((options.method || 'GET') === 'GET') return { path:'src/api.py', head_sha:'abc1234', blob_sha:'blob123', content:'def lookup():\\n return None\\n' }; return {path:'src/api.py', previous_head_sha:'abc1234', head_sha:'def5678', blob_sha:'blob456'}; }}); document.querySelector('#pull-sheet').classList.add('open'); document.querySelector('#pull-review').open = true; document.querySelector('#pull-files').innerHTML = detail.files.map((file, index) => createPullSheet.renderFile(file, index, false, value => String(value)) ).join(''); createPullSheet.bindFeedbackControls(document, suggestionController, () => suggestionItem, () => suggestionDetail, () => 'alex'); createPullSheet.review(detail, null, document); }""", detail, ) page.locator(".pull-review-feedback summary").click() page.locator('[data-address-review-feedback="42"]').click() assert page.locator("#pull-feedback-file-editor").is_hidden() assert page.locator("#review-pull-feedback-suggestion").is_visible() page.locator("#review-pull-feedback-suggestion").click() page.wait_for_function("suggestionCalls.length === 1") assert page.locator("#pull-feedback-suggestion-preview").is_visible() assert page.locator("#pull-feedback-suggestion-before").text_content() == " return None" assert page.locator("#pull-feedback-suggestion-after").text_content() == " return empty_result()" assert page.locator("#pull-feedback-file-editor").is_hidden() page.locator("#next-pull-feedback").click() assert page.locator("#pull-feedback-suggestion-preview").is_hidden() assert page.locator("#review-pull-feedback-suggestion").is_hidden() page.locator("#previous-pull-feedback").click() page.locator("#review-pull-feedback-suggestion").click() page.wait_for_function("suggestionCalls.length === 2") page.locator("#apply-pull-feedback-suggestion").click() page.wait_for_function("suggestionCalls.length === 3") result = page.evaluate("""() => ({ calls:suggestionCalls, head:suggestionDetail.head_sha, previewHidden:document.querySelector('#pull-feedback-suggestion-preview').hidden, status:document.querySelector('#pull-feedback-status').textContent, scrollWidth:document.documentElement.scrollWidth, clientWidth:document.documentElement.clientWidth, heights:Array.from(document.querySelectorAll('#pull-feedback-pass button')) .filter(button => button.getClientRects().length > 0).map(button => button.getBoundingClientRect().height), })""") browser.close() assert result["scrollWidth"] <= result["clientWidth"] assert min(result["heights"]) >= 44 assert result["head"] == "def5678" assert result["previewHidden"] is True assert "suggested change committed" in result["status"].lower() body = result["calls"][2]["options"]["body"] assert '"expected_blob_sha":"blob123"' in body assert '"content":"def lookup():\\n return empty_result()\\n"' in body