diff options
| author | Christophe Besson <cbesson@gmail.com> | 2026-09-07 10:35:09 +0200 |
|---|---|---|
| committer | Christophe Besson <cbesson@gmail.com> | 2026-09-07 10:35:09 +0200 |
| commit | 2c0903c648e24b4e2adf20492398e8b67d033b49 (patch) | |
| tree | 0435f298010f0f946362f28baebbe88337ca8768 /packages/meshbay-hub/tests/test_css_variables.py | |
| parent | 0ed078c92cabab1dab0f70f321562032ea549ce6 (diff) | |
| parent | eeda274d751c537f4ecef3087994a16a9517478f (diff) | |
| download | meshbay-2c0903c648e24b4e2adf20492398e8b67d033b49.tar.gz | |
Merge branch 'refactor/groups-phase1'
Groups refactor, phases 1-3.
The root model replaces the old `upload` flag and group-wide `member_upload`
with per-root `writable`/`removable`/`ejected`, carried by a `RootSet` that
both front doors — the loopback API and signed MNP — reach through the same
`ops` functions. MNP goes to 1.1, additively: the roots table now rides on
`index_delta`, so a root added, removed, ejected or plugged reaches every
connected client instead of only whoever reloaded.
The group UI becomes a plugin architecture: an application is a registry
entry in `apps.js` plus its own files, with directories stored generically
by `ops.set_app_directories` under whatever the app is called. A reference
application, hidden behind `?dev=1`, is what makes that claim testable —
adding it is what found the two places still naming apps by hand.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011pvMdvLBG92jyhvD5pD6us
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)) |