diff options
Diffstat (limited to 'packages/meshbay-hub/tests/test_sticky_band_ring.py')
| -rw-r--r-- | packages/meshbay-hub/tests/test_sticky_band_ring.py | 109 |
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)) |