summaryrefslogtreecommitdiffstats
path: root/packages/meshbay-hub/tests/test_page_does_not_scroll.py
diff options
context:
space:
mode:
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.py125
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">&#9776;</button>
+ <a class="nav-brand" href="#/">MeshBay</a></div>
+ <div class="nav-right"><a class="nav-notif" href="#/">&#128276;</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"