from __future__ import annotations import os import re from pathlib import Path import pytest if os.getenv("STACKCHAIN_RUN_RELEASE_E2E") != "1": pytest.skip("rendered mobile Search Preview checks run only in the browser gate", allow_module_level=True) pytest.importorskip("playwright.sync_api") from playwright.sync_api import expect, sync_playwright ROOT = Path(__file__).resolve().parents[2] FRONTEND = ROOT / "frontend" @pytest.mark.parametrize(("width", "height"), [(320, 568), (390, 844)]) def test_rendered_mobile_search_preview_navigation_preserves_reading_space_and_draft(width, height): html = re.sub(r'', "", (FRONTEND / "index.html").read_text()) with sync_playwright() as playwright: browser = playwright.chromium.launch() page = browser.new_page(viewport={"width": width, "height": height}) page.set_content(html) page.add_style_tag(path=FRONTEND / "dashboard.css") page.locator("#search-preview").evaluate("node => node.classList.add('open')") page.locator("#search-preview-reply-workspace").evaluate("node => node.hidden = false") page.locator(".search-preview-navigation").evaluate("node => node.hidden = false") page.locator("#search-preview-overview").evaluate("node => node.style.minHeight = '500px'") page.locator("#search-preview-conversation").evaluate("node => node.style.minHeight = '500px'") page.locator("#search-preview-reply-workspace").evaluate("node => node.style.minHeight = '500px'") page.locator("#search-preview-actions").evaluate("node => node.style.minHeight = '300px'") page.add_script_tag(path=FRONTEND / "search-preview.js") page.evaluate("""() => { const assignedIssue = { kind:'issue', state:'open', claimable:false, assigned_to_me:false, assignees:['alexander'], watching:false, }; globalThis.renderSearchPreviewWatch( assignedIssue, {status:'ready'}, document.querySelector('#watch-search-result') ); }""") page.add_script_tag(path=FRONTEND / "mobile-search-preview-nav.js") watch = page.locator("#watch-search-result") expect(watch).to_be_visible() expect(watch).to_have_text("Watch issue") watch_bounds = watch.bounding_box() assert watch_bounds and watch_bounds["height"] >= 44 navigation = page.locator(".mobile-search-preview-nav") expect(navigation).to_be_visible() buttons = navigation.locator("button:visible") assert navigation.locator("button").count() == 5 assert buttons.count() == 4 expect(navigation.locator('[data-search-preview-section="changes"]')).to_be_hidden() for button in buttons.all(): bounds = button.bounding_box() assert bounds and bounds["height"] >= 44 assert page.evaluate("document.documentElement.scrollWidth <= window.innerWidth") assert page.locator(".search-preview-panel").evaluate("node => node.scrollWidth <= node.clientWidth") page.locator("#search-preview-reply").fill("Keep this reply while navigating") navigation.locator('[data-search-preview-section="conversation"]').click() expect(navigation.locator('[data-search-preview-section="conversation"]')).to_have_attribute( "aria-current", "location" ) navigation.locator('[data-search-preview-section="reply"]').click() expect(page.locator("#search-preview-reply")).to_be_focused() assert page.locator("#search-preview-reply").input_value() == "Keep this reply while navigating" navigation.locator('[data-search-preview-section="actions"]').click() expect(navigation.locator('[data-search-preview-section="actions"]')).to_have_attribute( "aria-current", "location" ) continuation = page.locator(".search-preview-navigation") assert continuation.evaluate("node => getComputedStyle(node).position") == "sticky" assert continuation.evaluate("node => getComputedStyle(node).bottom") == "0px" assert page.locator("#search-preview-actions").evaluate("node => getComputedStyle(node).position") == "static" page.locator("#next-search-result").click() expect(navigation.locator('[data-search-preview-section="overview"]')).to_have_attribute( "aria-current", "location" ) assert page.locator("#search-preview-reply").input_value() == "Keep this reply while navigating" page.set_viewport_size({"width": 800, "height": 700}) expect(navigation).to_be_hidden() browser.close() @pytest.mark.parametrize(("width", "height"), [(320, 568), (390, 844)]) def test_rendered_search_to_preview_journey_contains_focus_until_final_close(width, height): html = re.sub(r'', "", (FRONTEND / "index.html").read_text()) with sync_playwright() as playwright: browser = playwright.chromium.launch() page = browser.new_page(viewport={"width": width, "height": height}) page.set_content(html) page.add_style_tag(path=FRONTEND / "dashboard.css") page.add_script_tag(path=FRONTEND / "mobile-search-modal.js") page.evaluate("""() => { const palette = document.querySelector('#cmd-palette'); const preview = document.querySelector('#search-preview'); palette.classList.add('open'); window.searchModalTest = createMobileSearchModal({document}); window.searchModalTest.activate(palette, { opener:document.querySelector('[data-mobile-task="search"]'), initialFocus:document.querySelector('#cmd-input'), }); preview.append(Object.assign(document.createElement('button'), {id:'dynamic-preview-action', textContent:'Dynamic action'})); }""") expect(page.locator("#cmd-input")).to_be_focused() expect(page.locator("#cmd-palette")).to_have_attribute("aria-modal", "true") assert page.locator("body > header").evaluate("node => node.inert") is True assert page.locator("main").evaluate("node => node.inert") is True assert page.locator("#mobile-task-dock").evaluate("node => node.inert") is True page.evaluate("""() => { document.querySelector('#cmd-palette').classList.remove('open'); document.querySelector('#search-preview').classList.add('open'); searchModalTest.transition(document.querySelector('#search-preview'), { initialFocus:document.querySelector('#close-search-preview'), }); document.querySelector('#dynamic-preview-action').focus(); }""") page.keyboard.press("Tab") expect(page.locator("#close-search-preview")).to_be_focused() assert page.locator("main").evaluate("node => node.inert") is True page.evaluate("""() => { document.querySelector('#search-preview').classList.remove('open'); document.querySelector('#cmd-palette').classList.add('open'); searchModalTest.transition(document.querySelector('#cmd-palette'), { initialFocus:document.querySelector('#cmd-input'), }); }""") expect(page.locator("#cmd-input")).to_be_focused() assert page.locator("main").evaluate("node => node.inert") is True assert page.evaluate("document.documentElement.scrollWidth <= window.innerWidth") page.evaluate("searchModalTest.deactivate()") expect(page.locator('[data-mobile-task="search"]')).to_be_focused() assert page.locator("body > header").evaluate("node => node.inert") is False assert page.locator("main").evaluate("node => node.inert") is False assert page.locator("#mobile-task-dock").evaluate("node => node.inert") is False browser.close()