summaryrefslogtreecommitdiffstats
path: root/packages/meshbay-hub/tests/test_layout_measured.py
diff options
context:
space:
mode:
Diffstat (limited to 'packages/meshbay-hub/tests/test_layout_measured.py')
-rw-r--r--packages/meshbay-hub/tests/test_layout_measured.py146
1 files changed, 146 insertions, 0 deletions
diff --git a/packages/meshbay-hub/tests/test_layout_measured.py b/packages/meshbay-hub/tests/test_layout_measured.py
new file mode 100644
index 0000000..91f1ed0
--- /dev/null
+++ b/packages/meshbay-hub/tests/test_layout_measured.py
@@ -0,0 +1,146 @@
+"""
+Layouts measured in a browser instead of read out of the stylesheet.
+
+`test_layout_responsive.py` says, in its own docstring, that a layout cannot be
+measured here because there is no browser in the suite. There is one now —
+Chrome is what the video work has been verified against — and the difference
+matters: `width: 330px` in a rule tells you nothing about whether the thing
+lands on the screen. That depends on where its anchor sits, which depends on
+everything to its right.
+
+The transfers panel is the case that proved it. 330 px wide, anchored to the
+right edge of its button — but that button is not at the right edge of the
+screen, the bell and the user menu come after it. Measured before the fix:
+
+ 320 px viewport -> panel at -138..192, 138 px off the left
+ 360 px -> -98..232
+ 412 px -> -46..284
+
+What is cut off is the left-hand side, which is where the file names are, so
+what was left on screen was a strip of progress bars belonging to nothing —
+reported as "on mobile you only see the right-hand side, without the content".
+
+Every assertion here is a rectangle, not a declaration.
+"""
+
+import json
+import shutil
+import subprocess
+import textwrap
+from pathlib import Path
+
+import pytest
+
+HARNESS = Path(__file__).parent / "harness" / "layout_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")
+
+# The nav as `Nav()` renders it: the transfers widget is not last — the bell and
+# the user menu follow it, which is the whole reason the panel hangs off.
+NAV = textwrap.dedent("""
+ <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">
+ <div class="transfer-wrap">
+ <button class="nav-notif transfer-btn">&#8595;</button>
+ <div class="transfer-panel">
+ <div class="transfer-head">Transfers<button class="btn-secondary">Clear</button></div>
+ <div class="transfer-item">
+ <div class="transfer-line">
+ <span class="transfer-kind">&#8595;</span>
+ <span class="transfer-name">S03E01. Salt and Sea, Fire and Blood.mp4</span>
+ <button class="transfer-cancel">&#10005;</button>
+ </div>
+ <div class="dl-progress"><div class="dl-fill" style="width:42%"></div></div>
+ <div class="transfer-meta"><span>210 MB / 493 MB</span><span>3.1 MB/s</span></div>
+ </div>
+ </div>
+ </div>
+ <a class="nav-notif" href="#/">&#128276;</a>
+ <div class="user-menu"><button class="nav-btn">someone</button></div>
+ </div>
+ </nav>
+""")
+
+
+WIDTHS = [320, 360, 412, 768, 1024]
+SELECTORS = [".transfer-panel", ".transfer-name"]
+
+
+@pytest.fixture(scope="module")
+def measured(tmp_path_factory):
+ """One browser for every width, because launching one apiece cost the
+ suite three minutes."""
+ fragment = tmp_path_factory.mktemp("layout") / "fragment.html"
+ fragment.write_text(NAV)
+ proc = subprocess.run(
+ ["python3", str(HARNESS), ",".join(str(w) for w in WIDTHS),
+ str(fragment), *SELECTORS],
+ 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}"
+ for w in WIDTHS:
+ assert out[str(w)]["viewport"]["w"] == w, (
+ f"asked for {w} px and measured at {out[str(w)]['viewport']['w']}")
+ return out
+
+
+def _box(measured, width: int, selector: str) -> dict:
+ return measured[str(width)]["boxes"][selector]
+
+
+@pytest.mark.parametrize("width", [320, 360, 412])
+def test_the_transfers_panel_fits_a_phone(measured, width):
+ """The reported defect, as a rectangle."""
+ box = _box(measured, width, ".transfer-panel")
+ assert box is not None, "the panel did not render"
+ assert box["offLeft"] == 0, (
+ f"{box['offLeft']} px of the panel is off the left of a {width} px "
+ "screen — and the left is where the file names are")
+ assert box["offRight"] == 0, (
+ f"{box['offRight']} px of the panel is off the right of a {width} px screen")
+
+
+@pytest.mark.parametrize("width", [320, 360])
+def test_the_file_name_gets_room_to_be_read(measured, width):
+ """Fitting on screen is not the same as being legible.
+
+ A panel could satisfy the test above by being narrow enough to show
+ nothing. The name is the one part a viewer needs.
+ """
+ box = _box(measured, width, ".transfer-name")
+ assert box is not None and box["width"] >= 180, (
+ f"the file name has {box['width'] if box else 0} px on a {width} px "
+ "screen, which is not enough to tell two downloads apart")
+
+
+def test_the_desktop_panel_is_untouched(measured):
+ """The fix is a media query, and it must stay inside it."""
+ box = _box(measured, 1024, ".transfer-panel")
+ assert box["width"] == 330, (
+ f"the desktop panel is now {box['width']} px — the mobile rule has "
+ "escaped its breakpoint")
+ assert box["offLeft"] == 0 and box["offRight"] == 0
+
+
+def test_it_does_not_stretch_across_a_tablet(measured):
+ """Pinned to both edges, a panel would be 750 px wide at 768."""
+ box = _box(measured, 768, ".transfer-panel")
+ assert box["width"] <= 440, (
+ f"{box['width']} px of panel on a tablet, which is a list of two "
+ "filenames stretched over most of the screen")
+
+
+@pytest.mark.parametrize("width", [320, 360, 412])
+def test_the_page_does_not_scroll_sideways(measured, width):
+ """The other half of "it fits": nothing pushed the document wider."""
+ r = measured[str(width)]
+ assert r["docScrollW"] <= r["viewport"]["w"], (
+ f"the document scrolls to {r['docScrollW']} px on a {width} px screen")