1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
|
"""
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")
|