summaryrefslogtreecommitdiffstats
path: root/packages/meshbay-hub/tests/test_sticky_band_ring.py
diff options
context:
space:
mode:
Diffstat (limited to 'packages/meshbay-hub/tests/test_sticky_band_ring.py')
-rw-r--r--packages/meshbay-hub/tests/test_sticky_band_ring.py109
1 files changed, 109 insertions, 0 deletions
diff --git a/packages/meshbay-hub/tests/test_sticky_band_ring.py b/packages/meshbay-hub/tests/test_sticky_band_ring.py
new file mode 100644
index 0000000..2d57822
--- /dev/null
+++ b/packages/meshbay-hub/tests/test_sticky_band_ring.py
@@ -0,0 +1,109 @@
+"""
+The first band's opaque ring falls on page background, never on page content.
+
+A pinned band paints a ring of page colour around itself, `--band-margin` wide,
+so the gap it keeps in the flow is still there once it pins and nothing shifts
+at the moment it does (style.css, "Sticky chrome"). The ring is drawn on all
+four sides — it has to be, or the corners a border radius leaves stay
+transparent and a row slides visibly through them.
+
+So a band needs that much clearance *above* it as well as below. Every band but
+the first has it for free: what is above it is the band it pins under, at a
+higher z-index, which a ring cannot paint over. The first band has the page's
+own content above it, and for a while nothing made the two agree — the
+join-code form under a group's title leaves 12px, the tab bar's ring is 16px,
+and the form came back with the bottom 4px of its field and its button painted
+over. Reported as "the form is slightly cut off", which is exactly what it
+looks like and says nothing about a stylesheet to whoever reports it.
+
+Both halves are asserted, and the second is the one with teeth. A margin-top on
+a *lower* band is not a harmless extra: the gap between two bands is the upper
+one's `--band-margin` and nothing else — the number `--chrome-h` carries and
+the offset the lower band pins at — and two adjoining margins collapse to the
+larger, so a lower band's own margin-top wins wherever it is bigger and leaves
+the flow a couple of pixels wider than the pinned layout. That is what writing
+this rule for all six bands did, in every media view at every phone width,
+before `test_sticky_header.py` measured it.
+
+Read out of the stylesheet rather than measured in a browser, deliberately.
+A browser shows the 4px at one width, in one of the states that happen to put
+something above a band; what has to hold is which bands are in which of two
+lists, and that is a fact about the source. `docs/apps.md` sends the author of
+a new application here to make its toolbar pin, and this is what says whether
+the toolbar they add needs the gap — it does not, and it must not have it.
+"""
+
+import re
+from pathlib import Path
+
+import pytest
+
+STYLE = (Path(__file__).resolve().parents[1] / "src" / "meshbay_hub"
+ / "static" / "style.css")
+
+pytestmark = pytest.mark.skipif(not STYLE.exists(),
+ reason="the stylesheet is not in this checkout")
+
+COMMENT = re.compile(r"/\*.*?\*/", re.S)
+RULE = re.compile(r"([^{}]+)\{([^{}]*)\}", re.S)
+# `.sticky-chrome > .group-tabs` and `.sticky-chrome > * + .group-tabs` name the
+# same band; which of them is written says which list the band is in.
+BAND = re.compile(r"^\.sticky-chrome\s*>\s*(?:\*\s*\+\s*)?(\S+)$")
+
+
+def _bands(declares) -> set[str]:
+ """The bands named by every rule whose body `declares` says yes to."""
+ source = COMMENT.sub("", STYLE.read_text(encoding="utf-8"))
+ found = set()
+ for selectors, body in RULE.findall(source):
+ if not declares(body):
+ continue
+ for selector in selectors.split(","):
+ match = BAND.match(" ".join(selector.split()))
+ if match:
+ found.add(match.group(1))
+ return found
+
+
+def _ringed() -> set[str]:
+ return _bands(lambda body: "box-shadow" in body
+ and "var(--band-margin)" in body)
+
+
+def _first_bands() -> set[str]:
+ """The bands that pin against the navigation bar rather than another band.
+
+ `top: var(--nav-h)` on its own, where every band below one of these pins at
+ a `calc()` that adds the heights above it.
+ """
+ return _bands(lambda body: re.search(
+ r"top:\s*var\(--nav-h\)\s*;", body))
+
+
+def _gapped() -> set[str]:
+ return _bands(lambda body: re.search(
+ r"margin-top:\s*var\(--band-margin\)", body))
+
+
+def test_the_band_under_the_navigation_bar_reserves_the_gap_its_ring_needs():
+ ringed, first = _ringed(), _first_bands()
+ assert ringed and first, (
+ "no band paints a ring, or none pins against the navigation bar — "
+ "either the mechanism is gone, in which case this test should be too, "
+ "or it was renamed and nothing here is being checked")
+
+ unguarded = sorted((ringed & first) - _gapped())
+ assert not unguarded, (
+ "these bands have the page's own content above them and paint a ring "
+ "of page colour on all four sides, but nothing keeps that much room "
+ "above them, so the ring lands on the content: " + ", ".join(unguarded))
+
+
+def test_no_band_under_another_band_reserves_one():
+ """The gap between two bands is the upper one's, and only the upper one's."""
+ lower = sorted(_gapped() - _first_bands())
+ assert not lower, (
+ "these bands pin under another band, so nothing can paint over them "
+ "and they need no room above — and the margin-top they have collapses "
+ "with the upper band's, taking the flow layout wider than the pinned "
+ "one wherever it is the larger of the two: " + ", ".join(lower))