Eight more test files state their properties without issue, milestone and rule numbers or dated regressions. The version tests describe what folding the channel into the version breaks instead of the date it did; the surface registry and settings-defaults tests state that floors move rather than quoting the decision that made them move. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
136 lines
5.6 KiB
Python
136 lines
5.6 KiB
Python
"""The starter role set.
|
||
|
||
The premise: a literal gets written when there is no role to reach for. A
|
||
house style with no "text on a filled colour" role collects `color: #fff` at
|
||
every call site that needs one. 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, 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 an issue is cited — so the
|
||
naive pattern flags documentation. 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():
|
||
"""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."""
|
||
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
|