Module and test docstrings state what is pinned without the record number or step that introduced it. Incident accounts become the failure shape in the present tense: the pipefail head, the code_shapes import, the export order, the facet dialects, the cross-kind repeat, the write-once kind. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
176 lines
6.7 KiB
Python
176 lines
6.7 KiB
Python
"""The CI-side token check.
|
|
|
|
The checker is stdlib-only and lives in scripts/ so CI can run it without an
|
|
install, so these tests import it by path rather than as a package.
|
|
|
|
Two properties matter more than the parsing: it must not fire on a COMMENT that
|
|
discusses a rule, and it must not fire on a longer literal that merely contains a
|
|
shorter one. Both would make the report untrustworthy, and an untrustworthy
|
|
report is worse than none — people stop reading it and the check stops working
|
|
while still passing.
|
|
"""
|
|
import importlib.util
|
|
import pathlib
|
|
|
|
import pytest
|
|
|
|
_PATH = pathlib.Path(__file__).resolve().parents[1] / "scripts" / "check_design_tokens.py"
|
|
_spec = importlib.util.spec_from_file_location("check_design_tokens", _PATH)
|
|
check = importlib.util.module_from_spec(_spec)
|
|
_spec.loader.exec_module(check)
|
|
|
|
|
|
SHEET = """
|
|
:root {
|
|
/* surface */
|
|
--fs-surface-page: #14171A; /* page bg */
|
|
--fs-text-primary: #E8E4D8;
|
|
}
|
|
|
|
/* SUPERSEDES — write the token, not the literal.
|
|
* #fff -> --fs-text-primary
|
|
* #ffffff -> --fs-text-primary
|
|
* bold -> --fs-weight-medium
|
|
*/
|
|
"""
|
|
|
|
|
|
# --- reading the sheet ------------------------------------------------------
|
|
|
|
def test_declarations_after_a_comment_are_not_lost():
|
|
"""Anchored on the colon alone. Anchoring on `{` or `;` silently drops every
|
|
declaration that follows a comment — a mistake made once already in this
|
|
codebase, which lost three tokens without erroring."""
|
|
assert check.declared_tokens(SHEET) == {"--fs-surface-page", "--fs-text-primary"}
|
|
|
|
|
|
def test_the_supersedes_block_is_read_from_the_sheet_not_hardcoded():
|
|
"""The checker knows nothing about any install's palette.
|
|
Everything it enforces comes out of the stylesheet it is pointed at."""
|
|
assert check.superseded_literals(SHEET) == {
|
|
"#fff": "--fs-text-primary",
|
|
"#ffffff": "--fs-text-primary",
|
|
"bold": "--fs-weight-medium",
|
|
}
|
|
|
|
|
|
def test_a_sheet_with_no_supersedes_block_yields_nothing():
|
|
assert check.superseded_literals(":root { --a: 1px; }") == {}
|
|
|
|
|
|
# --- the literal matcher ----------------------------------------------------
|
|
|
|
def test_a_short_hex_does_not_match_inside_a_longer_one():
|
|
"""`#fff` firing on `#ffffff` would send someone to change correct code."""
|
|
assert check._literal_pattern("#fff").search("color: #ffffff;") is None
|
|
assert check._literal_pattern("#fff").search("color: #fff;") is not None
|
|
|
|
|
|
def test_the_match_is_case_insensitive():
|
|
assert check._literal_pattern("#ffffff").search("color: #FFFFFF;") is not None
|
|
|
|
|
|
def test_a_keyword_does_not_match_inside_a_longer_word():
|
|
"""`bold` must not fire on `font-weight: bolder` or a class named
|
|
`.bold-label` — the boundary is what keeps the report readable."""
|
|
assert check._literal_pattern("bold").search("font-weight: bolder;") is None
|
|
assert check._literal_pattern("bold").search(".bold-label { }") is None
|
|
assert check._literal_pattern("bold").search("font-weight: bold;") is not None
|
|
|
|
|
|
# --- reading a component ----------------------------------------------------
|
|
|
|
def test_only_the_style_block_of_an_sfc_is_read(tmp_path):
|
|
"""A hex in a template attribute or a script string is not a stylesheet
|
|
violation, and reporting it would bury the ones that are."""
|
|
sfc = tmp_path / "X.vue"
|
|
sfc.write_text(
|
|
'<template><div data-x="#fff">white</div></template>\n'
|
|
'<script setup>const c = "#fff";</script>\n'
|
|
'<style scoped>.a { color: var(--fs-text-primary); }</style>\n'
|
|
)
|
|
css = check.style_source(sfc)
|
|
assert "--fs-text-primary" in css
|
|
assert "#fff" not in css
|
|
|
|
|
|
def test_a_comment_explaining_a_rule_is_not_a_violation(tmp_path):
|
|
"""LOAD-BEARING, and it fired on the first real run. A comment documenting
|
|
why a literal is avoided necessarily contains that literal — this codebase's
|
|
own stylesheet says so about `#fff`. A checker that flags the documentation
|
|
of a rule teaches people to stop documenting rules."""
|
|
sfc = tmp_path / "Y.vue"
|
|
sfc.write_text(
|
|
"<style scoped>\n"
|
|
"/* Deliberately NOT #fff — pure white is never text. */\n"
|
|
".a { color: var(--fs-text-primary); }\n"
|
|
"</style>\n"
|
|
)
|
|
css = check.style_source(sfc)
|
|
assert check._literal_pattern("#fff").search(css) is None
|
|
|
|
|
|
def test_a_plain_css_file_is_read_whole(tmp_path):
|
|
css_file = tmp_path / "shared.css"
|
|
css_file.write_text(".a { color: #fff; }")
|
|
assert "#fff" in check.style_source(css_file)
|
|
|
|
|
|
# --- the gate ---------------------------------------------------------------
|
|
|
|
def _run(tmp_path, sheet: str, component: str, monkeypatch, capsys):
|
|
(tmp_path / "assets").mkdir(parents=True, exist_ok=True)
|
|
sheet_path = tmp_path / "assets" / "theme.css"
|
|
sheet_path.write_text(sheet)
|
|
(tmp_path / "C.vue").write_text(f"<style>{component}</style>")
|
|
monkeypatch.setattr(
|
|
"sys.argv",
|
|
["check", "--sheet", str(sheet_path), "--root", str(tmp_path)],
|
|
)
|
|
code = check.main()
|
|
return code, capsys.readouterr().out
|
|
|
|
|
|
def test_an_unresolvable_reference_fails_the_build(tmp_path, monkeypatch, capsys):
|
|
"""The one hard gate. It is safe to gate on because the count is zero today —
|
|
a ratchet holding a line already reached, not a backlog that keeps CI red."""
|
|
code, out = _run(tmp_path, SHEET, ".a { color: var(--nope); }", monkeypatch, capsys)
|
|
assert code == 1
|
|
assert "--nope" in out
|
|
|
|
|
|
def test_a_resolvable_reference_passes(tmp_path, monkeypatch, capsys):
|
|
code, out = _run(
|
|
tmp_path, SHEET, ".a { color: var(--fs-text-primary); }", monkeypatch, capsys
|
|
)
|
|
assert code == 0
|
|
assert "every var() reference resolves" in out
|
|
|
|
|
|
def test_a_locally_declared_property_is_not_unresolved(tmp_path, monkeypatch, capsys):
|
|
"""A component may legitimately define its own custom property for local use —
|
|
a keyframe variable, a per-instance override. Only a reference to a name that
|
|
exists NOWHERE is broken."""
|
|
code, _ = _run(
|
|
tmp_path, SHEET, ".a { --local: 4px; padding: var(--local); }", monkeypatch, capsys
|
|
)
|
|
assert code == 0
|
|
|
|
|
|
def test_a_superseded_literal_reports_but_does_not_fail(tmp_path, monkeypatch, capsys):
|
|
"""Hundreds exist. Gating would make a permanently-red job, which is a check
|
|
nobody reads — worse than no check at all."""
|
|
code, out = _run(tmp_path, SHEET, ".a { color: #fff; }", monkeypatch, capsys)
|
|
assert code == 0
|
|
assert "#fff -> --fs-text-primary" in out
|
|
|
|
|
|
def test_a_missing_stylesheet_is_an_error_not_a_pass(tmp_path, monkeypatch, capsys):
|
|
"""If the sheet moves, the check must fail loudly rather than silently
|
|
passing with zero tokens to compare against — which would look identical to
|
|
a clean run."""
|
|
monkeypatch.setattr(
|
|
"sys.argv", ["check", "--sheet", str(tmp_path / "gone.css"), "--root", str(tmp_path)]
|
|
)
|
|
assert check.main() == 2
|