diff options
Diffstat (limited to 'packages/meshbay-hub/tests/test_css_variables.py')
| -rw-r--r-- | packages/meshbay-hub/tests/test_css_variables.py | 78 |
1 files changed, 78 insertions, 0 deletions
diff --git a/packages/meshbay-hub/tests/test_css_variables.py b/packages/meshbay-hub/tests/test_css_variables.py new file mode 100644 index 0000000..ec576d6 --- /dev/null +++ b/packages/meshbay-hub/tests/test_css_variables.py @@ -0,0 +1,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)) |