from pathlib import Path import pytest ROOT = Path(__file__).parents[2] @pytest.mark.parametrize("viewport", [(320, 568), (390, 844)]) def test_mobile_received_review_feedback_opens_matching_change(viewport): playwright = pytest.importorskip("playwright.sync_api") html = (ROOT / "frontend" / "index.html").read_text() detail = { "state": "open", "draft": False, "mergeable": True, "merged": False, "ci_state": "success", "head_sha": "current-head", "reviewers": [{ "login": "sam", "status": "outdated", "head_sha": "old-head-123456", "blocking": True, "summary": "Please handle the narrow empty state before merging.", "comments": [{ "path": "src/a/very/long/mobile/path/review_target.py", "body": "Return before parsing when the payload is empty.", "line": 12, }], }], "files": [{ "filename": "src/a/very/long/mobile/path/review_target.py", "status": "modified", "additions": 2, "deletions": 1, "diff_available": True, "diff_lines": ["@@ -10,2 +10,3 @@", "-parse(payload)", "+if not payload: return", "+parse(payload)"], }], } 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 => { document.querySelector('#pull-sheet').classList.add('open'); document.querySelector('#pull-review').open = true; createPullSheet.review(detail, null, document); document.querySelector('#pull-files').innerHTML = detail.files.map((file, index) => createPullSheet.renderFile(file, index, false, value => String(value) .replaceAll('&', '&').replaceAll('<', '<').replaceAll('>', '>')) ).join(''); }""", detail, ) page.locator(".pull-review-feedback summary").click() jump = page.locator('[data-review-feedback-file]') jump.click() metrics = page.evaluate( """() => { const jump = document.querySelector('[data-review-feedback-file]'); const toggle = document.querySelector('.pull-file-toggle'); const panel = document.getElementById(toggle.getAttribute('aria-controls')); return { scrollWidth: document.documentElement.scrollWidth, clientWidth: document.documentElement.clientWidth, jumpHeight: jump.getBoundingClientRect().height, expanded: toggle.getAttribute('aria-expanded'), panelHidden: panel.hidden, focused: document.activeElement === toggle, reviewedHead: document.querySelector('.pull-review-feedback').textContent, updatedReviewVisible: !document.querySelector('#request-updated-pull-review').hidden, }; }""" ) browser.close() assert metrics["scrollWidth"] <= metrics["clientWidth"] assert metrics["jumpHeight"] >= 44 assert metrics["expanded"] == "true" assert metrics["panelHidden"] is False assert metrics["focused"] is True assert "Reviewed head old-head" in metrics["reviewedHead"] assert metrics["updatedReviewVisible"] is True @pytest.mark.parametrize("viewport", [(320, 568), (390, 844)]) def test_mobile_pull_review_retries_inline_feedback_without_losing_core_review(viewport): playwright = pytest.importorskip("playwright.sync_api") html = (ROOT / "frontend" / "index.html").read_text() detail = { "state": "open", "draft": False, "mergeable": True, "merged": False, "ci_state": "success", "head_sha": "abc1234", "reviewers": [{ "login": "sam", "review_id": 8, "status": "changes_requested", "head_sha": "abc1234", "blocking": True, "summary": "Please handle the empty state.", }], "files": [], } 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 => { window.lazyFeedbackDetail = detail; window.lazyFeedbackItem = {repository:'stackchain/api', number:7, key:'stackchain/api#7'}; let attempts = 0; const controller = createPullSheet({ storage:null, fetchJson:() => { attempts += 1; if (attempts === 1) return Promise.reject(new Error('feedback unavailable')); return Promise.resolve({ review_id:8, head_sha:'abc1234', reviewed_head_sha:'abc1234', comments:[{id:99, path:'src/api.py', body:'Return before parsing.', line:12}], }); }, }); createPullSheet.bindFeedbackControls( document, controller, () => window.lazyFeedbackItem, () => window.lazyFeedbackDetail, () => 'timmy' ); document.querySelector('#pull-sheet').classList.add('open'); document.querySelector('#pull-review').open = true; const eligibility = createPullSheet.review(detail, null, document); document.querySelector('#pull-merge-state').textContent = eligibility.reason; }""", detail, ) page.locator(".pull-review-feedback summary").click() load = page.get_by_role("button", name="Load inline feedback") load.click() retry = page.get_by_role("button", name="Retry feedback") retry.wait_for() retry.click() page.get_by_text("Return before parsing.").wait_for() metrics = page.evaluate( """() => { const details = document.querySelector('.pull-review-feedback'); const address = document.querySelector('[data-address-review-feedback]'); return { scrollWidth: document.documentElement.scrollWidth, clientWidth: document.documentElement.clientWidth, blocker: document.querySelector('#pull-merge-state').textContent, feedback: details.textContent, addressHeight: address.getBoundingClientRect().height, }; }""" ) browser.close() assert metrics["scrollWidth"] <= metrics["clientWidth"] assert "requested changes" in metrics["blocker"] assert "Return before parsing." in metrics["feedback"] assert metrics["addressHeight"] >= 44