diff options
Diffstat (limited to 'packages/meshbay-hub/tests/test_page_does_not_scroll.py')
| -rw-r--r-- | packages/meshbay-hub/tests/test_page_does_not_scroll.py | 125 |
1 files changed, 125 insertions, 0 deletions
diff --git a/packages/meshbay-hub/tests/test_page_does_not_scroll.py b/packages/meshbay-hub/tests/test_page_does_not_scroll.py new file mode 100644 index 0000000..1187a7a --- /dev/null +++ b/packages/meshbay-hub/tests/test_page_does_not_scroll.py @@ -0,0 +1,125 @@ +""" +A page whose content fits the window must not offer a scrollbar. + +Twice now. First the sign-in card: `.layout` and `.page-center` each reserved +`100vh - 52px` and the second sat inside the first's 24px padding, so the +document was 48px too tall at every window size. Then the chat tab: the panel is +sized from JS to `viewport - top - 16`, which puts its bottom 16px above the +fold — but `.main` adds 24px of padding below it, so the document came out +**exactly 8px too tall, at every window size**, which is what "there is always a +scrollbar" means. + +Neither is visible in the stylesheet. Both are one subtraction against another, +in different files, and the only way to see them is to measure the document +against the window — which is what this does, running the real `fit()` lifted +out of `app.js` rather than a copy of it. +""" + +import json +import shutil +import subprocess +import textwrap +from pathlib import Path + +import pytest + +HARNESS = Path(__file__).parent / "harness" / "scroll_probe.py" +STATIC = Path(__file__).resolve().parents[1] / "src" / "meshbay_hub" / "static" + +pytestmark = pytest.mark.skipif( + shutil.which("google-chrome") is None or not (STATIC / "style.css").exists(), + reason="Chrome or the SPA stylesheet is not available") + +HEIGHTS = [700, 900, 1200] + +NAV_AND_SIDEBAR = """ +<nav class="nav"> + <div class="nav-left"><button class="nav-hamburger">☰</button> + <a class="nav-brand" href="#/">MeshBay</a></div> + <div class="nav-right"><a class="nav-notif" href="#/">🔔</a> + <div class="user-menu"><button class="nav-btn">someone</button></div></div> +</nav> +""" + +CHAT_TAB = NAV_AND_SIDEBAR + """ +<div class="layout"> + <aside class="sidebar"><div class="sidebar-section">Groups</div></aside> + <main class="main"> + <div class="group-header"><h2>a group</h2></div> + <div class="group-tabs"> + <button class="group-tab active">Chat</button> + <button class="group-tab">Files</button> + <button class="group-tab">Settings</button> + </div> + <div class="chat-panel"> + <div class="chat-messages"><p>hello</p></div> + <div class="chat-composer"><input type="text" /><button class="admin-btn">Send</button></div> + </div> + </main> +</div> +""" + +SHORT_PAGE = NAV_AND_SIDEBAR + """ +<div class="layout"> + <aside class="sidebar"><div class="sidebar-section">Groups</div></aside> + <main class="main"> + <h2>a group</h2> + <div class="settings-section"><p>not much here</p></div> + </main> +</div> +""" + + +def _measure(fragment: str, tmp_path: Path) -> dict: + path = tmp_path / "fragment.html" + path.write_text(fragment, encoding="utf-8") + proc = subprocess.run( + ["python3", str(HARNESS), str(path), ",".join(str(h) for h in HEIGHTS)], + capture_output=True, text=True, timeout=180) + assert proc.returncode == 0, f"probe failed: {proc.stdout}{proc.stderr}" + out = json.loads(proc.stdout) + assert "error" not in out, f"no measurement: {out}" + return out + + +@pytest.fixture(scope="module") +def chat(tmp_path_factory): + return _measure(CHAT_TAB, tmp_path_factory.mktemp("chat")) + + +@pytest.fixture(scope="module") +def short(tmp_path_factory): + return _measure(SHORT_PAGE, tmp_path_factory.mktemp("short")) + + +@pytest.mark.parametrize("height", HEIGHTS) +def test_the_chat_tab_fits_its_window(chat, height): + r = chat[str(height)] + assert r["overflow"] <= 0, ( + f"the document is {r['overflow']}px taller than the {height}px window — " + f"a scrollbar on the chat tab. Past the fold: {r['past']}") + + +@pytest.mark.parametrize("height", HEIGHTS) +def test_nothing_on_the_chat_tab_hangs_below_the_fold(chat, height): + """The composer is the one that matters: a chat you cannot type in.""" + assert chat[str(height)]["past"] == [] + + +@pytest.mark.parametrize("height", HEIGHTS) +def test_the_chat_panel_uses_the_room_it_has(chat, height): + """The correction must not overshoot. The panel should end just above the + fold, not halfway up the page — a 240px chat in a 1200px window would pass + every assertion above and be useless.""" + panel = chat[str(height)]["panel"] + assert panel, "no chat panel in the measurement" + gap = height - panel["bottom"] + assert 0 <= gap <= 40, ( + f"the panel ends {gap}px above the fold at {height}px") + + +@pytest.mark.parametrize("height", HEIGHTS) +def test_a_short_page_does_not_scroll_either(short, height): + """The control: without this, a chat panel shrunk to nothing would pass.""" + r = short[str(height)] + assert r["overflow"] <= 0, f"{r['overflow']}px of overflow with no content" |