aboutsummaryrefslogtreecommitdiffstats
path: root/packages/meshbay-hub/tests/test_layout_measured.py
diff options
context:
space:
mode:
authorChristophe Besson <cbesson@gmail.com>2026-10-12 01:27:00 +0200
committerChristophe Besson <cbesson@gmail.com>2026-10-12 01:27:00 +0200
commit3324b4eb701e34f64e859dd90937793ccf7f75c5 (patch)
treef22af245c36b76a1a158b91cc97f98df763ecb0f /packages/meshbay-hub/tests/test_layout_measured.py
parent14911aae21ec4975f7a805b91e3c4867cb41974b (diff)
downloadmeshbay-3324b4eb701e34f64e859dd90937793ccf7f75c5.tar.gz
feat: keep the sidebar for groups; search in the nav, the rest in the user menu
Search is an icon in the nav, left of the tray button and the bell, and marks itself current on its page. Administration, Node, Create group and Android Sync moved to the user menu, between Settings and the theme, each under the condition it had in the sidebar. The sidebar keeps Public groups and the groups. On a phone, a tablet or the Android application the menu trigger is the avatar alone (max-width 768px or a coarse pointer); the name is the dropdown's first line, one line with an ellipsis. Measured: a 20-character handle put the trigger 159 px off a 320 px screen with search and transfers in the nav, and a 64-character one pushed the dropdown 122 px off the left. Creating a group is a desktop-application feature by design: a hand-typed #/create-group no longer opens the form in a browser, and QUICKSTART step 6 now says to use the desktop application instead of the browser. test_layout_responsive read only the first 768px media block; it reads them all now. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Diffstat (limited to 'packages/meshbay-hub/tests/test_layout_measured.py')
-rw-r--r--packages/meshbay-hub/tests/test_layout_measured.py83
1 files changed, 81 insertions, 2 deletions
diff --git a/packages/meshbay-hub/tests/test_layout_measured.py b/packages/meshbay-hub/tests/test_layout_measured.py
index 57d325a..298a754 100644
--- a/packages/meshbay-hub/tests/test_layout_measured.py
+++ b/packages/meshbay-hub/tests/test_layout_measured.py
@@ -62,8 +62,12 @@ NAV = textwrap.dedent("""
</div>
</div>
</div>
+ <a class="nav-notif nav-search" href="#/search">
+ <svg class="icon" viewBox="0 0 24 24"></svg></a>
<a class="nav-notif" href="#/">&#128276;</a>
- <div class="user-menu"><button class="nav-btn">someone</button></div>
+ <div class="user-menu-wrap"><button class="user-menu-trigger">
+ <span class="user-menu-avatar">S</span><span class="user-menu-name">someone</span>
+ </button></div>
</div>
</nav>
""")
@@ -206,8 +210,12 @@ GROUPED = textwrap.dedent("""
</div>
</div>
</div>
+ <a class="nav-notif nav-search" href="#/search">
+ <svg class="icon" viewBox="0 0 24 24"></svg></a>
<a class="nav-notif" href="#/">&#128276;</a>
- <div class="user-menu"><button class="nav-btn">someone</button></div>
+ <div class="user-menu-wrap"><button class="user-menu-trigger">
+ <span class="user-menu-avatar">S</span><span class="user-menu-name">someone</span>
+ </button></div>
</div>
</nav>
""")
@@ -283,3 +291,74 @@ def test_the_waiting_bar_is_as_wide_as_a_progress_bar(grouped):
assert bar["width"] > 100, (
f"at {width} px the waiting bar is {bar['width']} px wide")
assert bar["height"] >= 3, "the waiting bar has no height"
+
+
+# ── The whole nav on a phone ────────────────────────────────────────────────
+#
+# Search moved from the sidebar to the nav, beside the bell. With the user's
+# name in the menu trigger, a 20-character handle already put the trigger 79 px
+# off a 320 px screen, and 159 px once search and a transfer were there too.
+# The fixture is the real markup of `Nav()` and `UserMenu()`, with the icons as
+# the 1em boxes `Icon()` draws.
+
+_ICON = '<svg class="icon" viewBox="0 0 24 24"></svg>'
+FULL_NAV = textwrap.dedent(f"""
+ <nav class="nav">
+ <div class="nav-left">
+ <button class="nav-hamburger">{_ICON}</button>
+ <a class="nav-brand" href="#/"><img class="nav-brand-m" alt="M">eshBay</a>
+ </div>
+ <div class="nav-right">
+ <div class="transfer-wrap">
+ <button class="nav-notif transfer-btn">{_ICON}<span class="notif-badge">2</span></button>
+ </div>
+ <a class="nav-notif nav-search" href="#/search">{_ICON}</a>
+ <a class="nav-notif" href="#/">{_ICON}<span class="notif-badge">3</span></a>
+ <div class="user-menu-wrap">
+ <button class="user-menu-trigger">
+ <span class="user-menu-avatar">A</span>
+ <span class="user-menu-name">a_rather_long_handle</span>
+ <svg class="icon user-menu-caret" viewBox="0 0 24 24"></svg>
+ </button>
+ </div>
+ </div>
+ </nav>
+""")
+FULL_NAV_WIDTHS = [320, 360, 412, 521, 768, 1024]
+
+
+@pytest.fixture(scope="module")
+def full_nav(tmp_path_factory):
+ fragment = tmp_path_factory.mktemp("fullnav") / "fragment.html"
+ fragment.write_text(FULL_NAV, encoding="utf-8")
+ proc = subprocess.run(
+ ["python3", str(HARNESS), ",".join(str(w) for w in FULL_NAV_WIDTHS),
+ str(fragment), ".nav-brand", ".nav-right", ".user-menu-trigger",
+ ".user-menu-name"],
+ 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.mark.parametrize("width", FULL_NAV_WIDTHS)
+def test_the_user_menu_stays_inside_the_nav(full_nav, width):
+ """Off the right edge is where nobody can open it — and it holds sign-out."""
+ boxes = full_nav[str(width)]["boxes"]
+ trigger = boxes[".user-menu-trigger"]
+ assert trigger["right"] <= width - 16, (
+ f"at {width} px the user menu ends at {trigger['right']} px, past the "
+ "nav's own padding")
+ assert boxes[".nav-right"]["left"] >= boxes[".nav-brand"]["right"], (
+ f"at {width} px the nav's icons slide under the wordmark")
+
+
+@pytest.mark.parametrize("width", FULL_NAV_WIDTHS)
+def test_the_name_leaves_the_trigger_on_mobile_only(full_nav, width):
+ """Avatar alone up to the mobile breakpoint; the name beside it on a desktop."""
+ name = full_nav[str(width)]["boxes"][".user-menu-name"]
+ shown = bool(name and name["width"] > 0)
+ assert shown == (width > 768), (
+ f"at {width} px the name in the user menu is "
+ f"{'shown' if shown else 'hidden'}")