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
|
"""
Every `:hover` rule in style.css is inside `@media (hover: hover)`.
A tap on a touchscreen puts the element in `:hover` and nothing takes it out
until another element is tapped, so a button tapped once kept its hover colour
as if still pressed. It was fixed for the music player's buttons alone, then
reported again on the pager arrows, the nav hamburger and the rest: a rule per
button only moves the bug to the next button. This holds the whole file.
"""
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")
def _hover_rules(source: str) -> list[tuple[int, list[str], str]]:
"""(line, enclosing at-rule preludes, selector) for each rule naming :hover."""
found = []
stack: list[str] = []
start = 0
i = 0
while i < len(source):
if source.startswith("/*", i):
i = source.index("*/", i) + 2
start = i
continue
c = source[i]
if c == "{":
prelude = source[start:i].strip()
if ":hover" in prelude and not prelude.startswith("@"):
found.append((source.count("\n", 0, i) + 1, list(stack), prelude))
stack.append(prelude)
start = i + 1
elif c == "}":
stack.pop()
start = i + 1
elif c == ";":
start = i + 1
i += 1
return found
def test_every_hover_rule_is_scoped_to_devices_that_hover():
rules = _hover_rules(STYLE.read_text(encoding="utf-8"))
assert rules, "no :hover rule found, did the parser break?"
loose = [f"line {line}: {sel}" for line, stack, sel in rules
if not any("(hover: hover)" in at for at in stack)]
assert not loose, ("wrap these in @media (hover: hover), a tap leaves them "
"stuck on a touchscreen:\n" + "\n".join(loose))
def test_the_parser_catches_a_loose_rule():
rules = _hover_rules("/* a { } */ .a:hover { color: red; }\n"
"@media (hover: hover) { .b:hover { color: red; } }")
assert [(sel, bool(stack)) for _, stack, sel in rules] == [
(".a:hover", False), (".b:hover", True)]
|