fix(ui/ux): only lay out the import dialog while it is open

This commit is contained in:
Steppenstreuner
2026-08-29 05:10:52 +02:00
parent e3701ceb43
commit 87b139e236
2 changed files with 105 additions and 1 deletions
+4 -1
View File
@@ -107,7 +107,10 @@ dialog label { display: flex; flex-direction: column; gap: 0.2rem; margin: 0.5re
.series-list .gap { padding: 0.35rem 0.6rem; color: var(--muted); font-size: 0.85rem; }
/* the dialog keeps head and foot in place; only the results scroll, so the
"manuell anlegen" panel stays reachable no matter how many hits came back */
#quick-dialog { display: flex; flex-direction: column; max-height: 85vh; width: min(680px, 92vw); }
/* [open] matters: a bare "#quick-dialog { display: flex }" would override the
browser's display:none for a closed dialog and leave it stuck on screen */
#quick-dialog[open] { display: flex; flex-direction: column; }
#quick-dialog { max-height: 85vh; width: min(680px, 92vw); }
#quick-scroll { flex: 1 1 auto; min-height: 4rem; overflow-y: auto; margin-top: 0.6rem; }
#quick-results { margin: 0; }
#quick-foot { flex: none; border-top: 1px solid var(--border); margin-top: 0.6rem; padding-top: 0.6rem; }
+101
View File
@@ -0,0 +1,101 @@
"""Browser checks for the dialogs.
pytest alone cannot catch CSS mistakes: a "#quick-dialog { display: flex }"
overrides the browser's display:none for a closed <dialog> and leaves it stuck
on screen. Skipped when playwright or its browsers are missing.
"""
import socket
import subprocess
import sys
import time
from pathlib import Path
import pytest
sync_api = pytest.importorskip("playwright.sync_api", reason="playwright not installed")
def _free_port() -> int:
with socket.socket() as s:
s.bind(("127.0.0.1", 0))
return s.getsockname()[1]
@pytest.fixture(scope="module")
def server(tmp_path_factory):
tmp = tmp_path_factory.mktemp("ui")
downloads = tmp / "downloads"
(downloads / "Eine Folge").mkdir(parents=True)
for i in (1, 2):
(downloads / "Eine Folge" / f"{i:02d}.mp3").write_bytes(b"")
port = _free_port()
proc = subprocess.Popen(
[sys.executable, "-m", "uvicorn", "wordarr.main:app", "--port", str(port)],
cwd=Path(__file__).resolve().parent.parent,
env={"PATH": "/usr/bin:/bin", "WORDARR_CONFIG_DIR": str(tmp / "config"),
"WORDARR_DOWNLOAD_DIR": str(downloads)},
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL,
)
url = f"http://127.0.0.1:{port}"
for _ in range(100):
if proc.poll() is not None:
pytest.skip("uvicorn did not start")
try:
with socket.create_connection(("127.0.0.1", port), timeout=0.2):
break
except OSError:
time.sleep(0.1)
else:
proc.kill()
pytest.skip("server did not come up")
yield url
proc.kill()
@pytest.fixture
def page(server):
with sync_api.sync_playwright() as pw:
try:
browser = pw.chromium.launch()
except Exception as exc: # browsers not downloaded
pytest.skip(f"no chromium: {exc}")
page = browser.new_page(viewport={"width": 1200, "height": 900})
errors = []
page.on("pageerror", lambda e: errors.append(str(e)))
page.goto(server, wait_until="networkidle")
yield page
assert not errors, errors
browser.close()
def test_dialogs_start_closed(page):
for dialog in ("#quick-dialog", "#series-dialog", "#manual-dialog"):
assert not page.locator(dialog).is_visible(), dialog
# the import dialog lives inside the import section, so it can only be seen
# once that tab is open - a reload would not have hidden a broken one
page.click("nav button[data-view=import]")
assert not page.locator("#quick-dialog").is_visible()
def test_import_dialog_opens_and_closes(page):
page.click("nav button[data-view=import]")
page.click("#scan-btn")
page.wait_for_selector("[data-quick]")
page.click("[data-quick='0']")
page.wait_for_selector("#quick-dialog[open]")
assert page.locator("#quick-dialog").is_visible()
# everything reachable, nothing cut off at the bottom
for sel in ("#quick-m-submit", "#quick-close"):
box = page.locator(sel).bounding_box()
assert box["y"] + box["height"] <= 900, sel
page.click("#quick-close")
page.wait_for_timeout(150)
assert not page.locator("#quick-dialog").is_visible()
page.click("[data-quick='0']")
page.wait_for_selector("#quick-dialog[open]")
page.keyboard.press("Escape")
page.wait_for_timeout(150)
assert not page.locator("#quick-dialog").is_visible()