"""The starter role set (#2349). The premise: a literal gets written when there is no role to reach for. This codebase demonstrated it — the house style had no "text on a filled colour" role, so 76 call sites wrote `color: #fff` (#2275). Naming the roles at creation removes the occasion. The tests that matter here are about the BOUNDARY, not the content. Roles ship with the product; values never do. A default palette would be one operator's taste shipped as product code (rule #115), and it would be very easy to add by accident while "being helpful". """ import re import pytest from scribe.services import design_starter_roles as roles def test_no_role_carries_a_VALUE(): """THE rule-115 guard. Every seeded token is a named question with no answer. The moment one ships a hex, the product is prescribing an install's palette.""" for row in roles.starter_tokens(): assert row["value_by_mode"] == {}, f"{row['name']} shipped a value" def _colour_literals(text: str) -> list[str]: """Hex colours in `text`, NOT counting issue references. `#2275` is four hex-valid digits and also how this codebase cites an issue — so the naive pattern flags its own documentation, which is the third time that has happened here (#2353). A real colour either contains a letter a–f or is a full 6/8-digit value; an all-decimal 3- or 4-digit match is an issue number. """ out = [] for m in re.findall(r"#[0-9a-fA-F]{3,8}\b", text): digits = m[1:] if len(digits) not in (3, 4, 6, 8): continue if len(digits) in (6, 8) or any(c in "abcdefABCDEF" for c in digits): out.append(m) return out def test_the_module_contains_no_colour_literals_at_all(): """Belt and braces on the above, and the stronger claim: not just that tokens are blank, but that no palette hides in a comment or a docstring waiting to be pasted in. Checks the SOURCE, not the output.""" import pathlib src = pathlib.Path(roles.__file__).read_text() hexes = _colour_literals(src) assert not hexes, f"colour literals in product code: {hexes}" def test_the_colour_check_does_not_flag_issue_references(): """Pins the exclusion above, because without it this file fails on its own citations and the obvious 'fix' is to delete the check.""" assert _colour_literals("see #2275 and #2349") == [] assert _colour_literals("color: #fff") == ["#fff"] assert _colour_literals("#E8E4D8 on #14171A") == ["#E8E4D8", "#14171A"] assert _colour_literals("#000000") == ["#000000"] def test_every_group_is_individually_selectable(): """Operator's call: one flat list, all skippable. An install that wants three tokens must be able to get three.""" only_text = roles.starter_tokens(["text"]) assert {r["group_name"] for r in only_text} == {"text"} assert len(only_text) == 4 def test_empty_selection_yields_nothing_and_is_not_the_same_as_None(): """`[]` is a real answer — "none of them" — and must not be read as "unspecified, so give me everything". Getting this backwards would seed 40 rows into a system whose creator explicitly declined.""" assert roles.starter_tokens([]) == [] assert len(roles.starter_tokens(None)) > 30 def test_unknown_group_names_are_ignored_not_fatal(): """This feeds a checkbox list. A stale name from an older client should not fail an otherwise-fine creation.""" out = roles.starter_tokens(["text", "not-a-real-group"]) assert {r["group_name"] for r in out} == {"text"} def test_the_prefix_is_the_installs_choice(): """`--fs-` is FabledSword's convention, not the product's. Baking it in would put one family's naming into every install.""" assert all(r["name"].startswith("--ds-") for r in roles.starter_tokens(["text"])) custom = roles.starter_tokens(["text"], prefix="--acme-") assert all(r["name"].startswith("--acme-") for r in custom) assert "--acme-text-primary" in {r["name"] for r in custom} def test_text_on_action_is_in_the_starter_set(): """The specific role whose absence produced 76 literals. It is separate from text-primary on purpose: the surfaces it sits on do not change with the mode, while the page does — so reusing text-primary there passes in dark and fails contrast in light (#2275).""" names = {r["name"] for r in roles.starter_tokens(["text"])} assert "--ds-text-on-action" in names assert "--ds-text-primary" in names def test_names_are_valid_custom_properties(): """They go straight into a stylesheet; an invalid name is a silent no-op rather than an error, which is the worst failure mode available.""" valid = re.compile(r"^--[A-Za-z0-9_-]+$") for row in roles.starter_tokens(): assert valid.match(row["name"]), row["name"] def test_no_duplicate_names_across_the_whole_set(): """A design system has a partial-unique index on (system, name); a duplicate in the starter set would make creation fail at the DB with a constraint error rather than anything legible.""" names = [r["name"] for r in roles.starter_tokens()] assert len(names) == len(set(names)) def test_every_role_states_a_purpose(): """An unfilled role is only useful if it says what belongs there. "Colour 1" is a blank with extra steps.""" for row in roles.starter_tokens(): assert row["purpose"].strip(), row["name"] def test_describe_groups_matches_what_starter_tokens_produces(): """The catalogue a UI renders and the rows creation writes must not drift — a checklist offering a group that seeds nothing is a lie in the UI.""" described = {g["group"]: g["token_count"] for g in roles.describe_groups()} for group, count in described.items(): assert len(roles.starter_tokens([group])) == count