aboutsummaryrefslogtreecommitdiffstats
path: root/packages/meshbay-hub/tests/test_sidebar_legal_measured.py
blob: 3c44d8312a084796e0743f32428ec33413323357 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
"""
The legal link at the foot of the sidebar, measured in a browser.

It is pinned to the bottom of the window, not of the page: the sidebar sticks
under the navigation bar at the window's height, so on a long file list the
link is still on screen. And the music bar, pinned to the bottom of the window
as well, must not cover it — the bar publishes its height as `--music-bar-h`
(sticky.js, as the toolbars do), and the sidebar stops above it. That value
is set by hand here; what is under test is the stylesheet's arithmetic.

On a phone the sidebar is the slide-out panel, measured open.
"""

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 probe's frames are this tall.
WINDOW_H = 740
MUSIC_BAR_H = 64
INDEX_DOCK_H = 52
WIDTHS = [390, 1024, 1440]


def _page(music: bool, dock: bool = False) -> str:
    groups = "".join(
        f'<a class="sidebar-item sidebar-group" href="#/"><span class="si-head">'
        f'<span class="sidebar-item-name">Some group {i}</span></span></a>' for i in range(4))
    bar = (f'<div class="music-player-bar" style="height:{MUSIC_BAR_H}px;'
           f'box-sizing:border-box">a track</div>') if music else ""
    # Rendered right before the music bar, as app.js does.
    if dock:
        bar = (f'<div class="index-dock" style="height:{INDEX_DOCK_H}px;'
               f'box-sizing:border-box">indexing</div>') + bar
    props = []
    if music:
        props.append(f"--music-bar-h:{MUSIC_BAR_H}px")
    if dock:
        props.append(f"--index-dock-h:{INDEX_DOCK_H}px")
    style = f' style="{";".join(props)}"' if props else ""
    return textwrap.dedent(f"""
      <div id="app"{style}>
      <nav class="nav"><div class="nav-left"><a class="nav-brand" href="#/">MeshBay</a></div>
        <div class="nav-right"><button class="nav-btn">someone</button></div></nav>
      <div class="layout">
        <aside class="sidebar open">
          <div class="sidebar-section"><div class="sidebar-heading">My groups</div>{groups}</div>
          <a class="sidebar-legal" href="/legal/">Legal information</a>
        </aside>
        <main class="main"><h2>A long list</h2><div style="height:3000px"></div></main>
      </div>
      {bar}
      </div>
    """)


def _measure(tmp_path_factory, music: bool, dock: bool = False) -> dict:
    fragment = tmp_path_factory.mktemp("sidebar") / "fragment.html"
    fragment.write_text(_page(music, dock), encoding="utf-8")
    proc = subprocess.run(
        ["python3", str(HARNESS), ",".join(str(w) for w in WIDTHS),
         str(fragment), ".sidebar-legal", ".sidebar", ".music-player-bar", ".index-dock"],
        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 plain(tmp_path_factory):
    return _measure(tmp_path_factory, music=False)


@pytest.fixture(scope="module")
def playing(tmp_path_factory):
    return _measure(tmp_path_factory, music=True)


@pytest.fixture(scope="module")
def indexing(tmp_path_factory):
    return _measure(tmp_path_factory, music=True, dock=True)


@pytest.mark.parametrize("width", WIDTHS)
def test_the_link_is_at_the_bottom_of_the_window(plain, width):
    """Not at the bottom of a 3000px page, where nobody would find it."""
    link = plain[str(width)]["boxes"][".sidebar-legal"]
    assert link is not None, "no legal link"
    bottom = link["top"] + link["height"]
    assert WINDOW_H - 8 <= bottom <= WINDOW_H, (
        f"at {width} px the link ends at {bottom}, the window at {WINDOW_H}")
    assert link["offLeft"] == 0 and link["offRight"] == 0


@pytest.mark.parametrize("width", WIDTHS)
def test_the_music_bar_does_not_cover_it(playing, width):
    link = playing[str(width)]["boxes"][".sidebar-legal"]
    bar = playing[str(width)]["boxes"][".music-player-bar"]
    assert link is not None and bar is not None
    bottom = link["top"] + link["height"]
    assert bottom <= bar["top"] + 1, (
        f"at {width} px the link ends at {bottom} and the music bar starts at {bar['top']}")
    assert bottom >= bar["top"] - 8, (
        f"at {width} px the link floats {bar['top'] - bottom} px above the music bar")


@pytest.mark.parametrize("width", WIDTHS)
def test_the_indexing_dock_stacks_on_the_music_bar_and_covers_nothing(indexing, width):
    """The dock (index-dock.js) pins right above the music bar, off
    `--music-bar-h`, and the sidebar stops above both."""
    link = indexing[str(width)]["boxes"][".sidebar-legal"]
    dock = indexing[str(width)]["boxes"][".index-dock"]
    bar = indexing[str(width)]["boxes"][".music-player-bar"]
    assert link is not None and dock is not None and bar is not None
    assert abs(bar["top"] + bar["height"] - WINDOW_H) <= 1, (
        f"at {width} px the music bar ends at {bar['top'] + bar['height']}")
    assert abs(dock["top"] + dock["height"] - bar["top"]) <= 1, (
        f"at {width} px the dock ends at {dock['top'] + dock['height']} "
        f"and the music bar starts at {bar['top']}")
    bottom = link["top"] + link["height"]
    assert dock["top"] - 8 <= bottom <= dock["top"] + 1, (
        f"at {width} px the link ends at {bottom} and the dock starts at {dock['top']}")