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
|
"""
Every `var(--x)` names a variable this stylesheet defines.
CSS fails silently and generously: an unknown custom property makes the whole
declaration invalid, and the rule around it still applies. So a panel written
`background: var(--bg-panel)` when the palette calls it `--bg-surface` does not
error, does not warn, and does not look obviously wrong in a diff — it just has
no background, and the page shows straight through the modal.
That is not hypothetical. It shipped in the folder picker, and the same file
already carried one from before: `.notif-badge` asked for `--danger` where the
palette says `--error`, so the unread count was white text on nothing. Found by
a person looking at a screenshot, which is the only thing that was going to
find it.
A fallback (`var(--x, #ef4444)`) is a lesser version of the same mistake: the
declaration is valid and renders, but the name is still fiction, and the next
reader is told a variable exists that does not. Those are reported separately.
There was a third check here, comparing the dark palette against the light one
for anything a theme must not inherit. It fired on `--border-focus`, which is
a focus ring deliberately shared by both themes — correct code. A heuristic
that has to be explained away on its first run is worse than no test, so it is
gone rather than exempted.
"""
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")
# Where a custom property is defined. Two forms, and both had to be learned
# the hard way while writing this: a scoped one written inline
# (`.video-overview-wrap { --ov-lh: 1.5em; --ov-lines: 3; }`), which an
# anchored pattern misses, and one preceded by an explanatory comment, which a
# `[{;]`-prefixed pattern misses because the character before it is `/`. Either
# mistake reports correct code as broken, which is the fastest way to have a
# test like this ignored.
DEFINE = re.compile(r"(?:^|[{;])\s*(--[A-Za-z0-9_-]+)\s*:", re.M)
# `var(--name` and `var(--name, fallback`.
USE = re.compile(r"var\(\s*(--[A-Za-z0-9_-]+)\s*(,)?")
def _text() -> str:
return STYLE.read_text(encoding="utf-8")
def test_every_variable_used_without_a_fallback_is_defined():
source = _text()
defined = set(DEFINE.findall(source))
assert defined, "no custom properties found — did the palette move?"
missing = sorted({name for name, fallback in USE.findall(source)
if not fallback and name not in defined})
assert not missing, (
"used but never defined, so every declaration naming one of these is "
"invalid and silently does nothing:\n " + "\n ".join(missing))
def test_a_fallback_does_not_excuse_an_unknown_name():
"""
`var(--danger, #ef4444)` renders, so it is not the same bug — but it is the
same mistake, and it will read as intentional to the next person. Reported
so the name gets corrected rather than the fallback relied on.
"""
source = _text()
defined = set(DEFINE.findall(source))
guessed = sorted({name for name, fallback in USE.findall(source)
if fallback and name not in defined})
assert not guessed, (
"used with a fallback but not defined anywhere — rename to the real "
"variable:\n " + "\n ".join(guessed))
|