"""The CI-side token check (#2277). 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(): """Rule #115: the checker must know 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( '\n' '\n' '\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( "\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"") 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