"""
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 = """
"""
CHAT_TAB = NAV_AND_SIDEBAR + """
a group
hello
"""
SHORT_PAGE = NAV_AND_SIDEBAR + """
a group
not much here
"""
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"