""" A sidebar with many groups scrolls on its own, on a phone and on a desktop. Measured, not read: twelve groups beside a page long enough to scroll, in Chrome, at 390 and 1280 px. The list must scroll, its last group and the legal link must be reachable, and reaching an end must not hand the scroll on to the page behind — which it did, measured before the fix (`overscroll-behavior: auto`). What no headless measurement shows is a phone's address bar: an iframe has no dynamic toolbar, so `100vh` and `100dvh` agree here. That half is held by reading the stylesheet, the weaker evidence, and is the known behaviour of mobile browsers: `100vh` is the height with the bar hidden. """ import json import re import shutil import subprocess import sys from pathlib import Path import pytest HARNESS = Path(__file__).parent / "harness" / "sidebar_scroll_probe.py" STYLE = Path(__file__).resolve().parents[1] / "src" / "meshbay_hub" / "static" / "style.css" @pytest.fixture(scope="module") def measured(): if shutil.which("google-chrome") is None: pytest.skip("Chrome is not available") proc = subprocess.run([sys.executable, str(HARNESS)], capture_output=True, text=True, timeout=90) data = json.loads(proc.stdout.strip().splitlines()[-1]) assert "error" not in data, f"probe failed: {proc.stdout}{proc.stderr}" return data @pytest.mark.parametrize("width", ["390", "1280"]) def test_the_list_scrolls_inside_the_sidebar(measured, width): m = measured[width] assert m["overflowY"] == "auto" assert m["scrollHeight"] > m["clientHeight"], "twelve groups should overflow" assert m["sidebarBottom"] <= m["viewportHeight"], "the sidebar runs off the window" @pytest.mark.parametrize("width", ["390", "1280"]) def test_the_end_of_the_list_can_be_reached(measured, width): m = measured[width] assert m["lastGroupBottom"] <= m["sidebarBottom"] assert m["legalBottom"] <= m["sidebarBottom"] + 1 @pytest.mark.parametrize("width", ["390", "1280"]) def test_reaching_an_end_does_not_scroll_the_page(measured, width): assert measured[width]["overscrollBehaviorY"] == "contain" def test_the_height_follows_the_visible_viewport_with_a_fallback(): css = STYLE.read_text(encoding="utf-8") rules = [m.group(1) for m in re.finditer(r"\n\s*\.sidebar \{(.*?)\}", css, re.S)] assert len(rules) == 2, "expected the desktop and the phone .sidebar rules" for body in rules: heights = re.findall(r"height:\s*calc\((100d?vh)", body) assert heights == ["100vh", "100dvh"], ( f"a .sidebar rule sizes itself with {heights}: 100dvh is what keeps the " "foot of the list above a phone's address bar, after a 100vh fallback")