From bc60b4144545ccf136389f3992903adf3f2f87cb Mon Sep 17 00:00:00 2001 From: Chuck <33324927+ChuckBuilds@users.noreply.github.com> Date: Mon, 14 Sep 2026 13:03:23 -0400 Subject: [PATCH] test(element-style): cover ElementStyleResolver's colour clamp path (#572) * fix(colour): clamp out-of-range text_color components instead of dropping them _normalize_color returned None for a triple with a component outside 0..255, and None means "not configured" to element_color -- so configuring [300, 0, 20] silently handed the element its *default* colour rather than red. Every scoreboard reads its per-element colours through this path, so the bug reached all eight. It is also the odd one out: sports_card.coerce_rgb and SportsShared._coerce_rgb both clamp, and core's own test is named test_coerce_rgb_clamps_rather_than_rejecting. The rejecting normaliser arrived with the shared readers in 82a65ad2 (#425) while the eight plugins' colour tests kept asserting the clamping behaviour they had before, so the two sides have disagreed ever since. Clamped inline rather than delegating to coerce_rgb: sports_card already imports element_style, so importing back would be circular. Adds the core assertion whose absence let this drift -- element_color had no test covering an out-of-range component. Co-Authored-By: Claude Opus 5 (1M context) * test(element-style): cover ElementStyleResolver's own colour clamp path CodeRabbit flagged that the new sports_card clamp regression test only exercises element_color(); ElementStyleResolver._resolve() normalizes configured colours through a separate call to the same _normalize_color, comparing against a schema/classic reference to decide user_forced_color. Add a resolver-level case so a future regression in that path (e.g. going back to rejecting out-of-range components instead of clamping) is caught too. Mutation-checked: fails if _normalize_color rejects instead of clamps. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01KP6kWxjUtJi72c56GaMmC8 --------- Co-authored-by: Claude Opus 5 (1M context) --- test/test_element_style.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/test/test_element_style.py b/test/test_element_style.py index 3352b47a..b07871cd 100644 --- a/test/test_element_style.py +++ b/test/test_element_style.py @@ -306,6 +306,18 @@ class TestUserOverrides: assert not style.user_forced assert style.color == (255, 0, 0) + def test_color_override_clamps_out_of_range_components(self, style_schema_path): + """The resolver path normalizes colour separately from element_color + (see src/element_style.py's _resolve) -- a rejection there reads as + "not configured" and the element would silently get the default + colour back instead of the clamped one the user asked for.""" + config = {"customization": {"title_text": {"text_color": [999, -5, 20]}}} + style = _resolver(config, style_schema_path).style( + "title_text", classic_font="PressStart2P-Regular.ttf", + classic_size=8, classic_color=(255, 255, 255)) + assert style.user_forced_color + assert style.color == (255, 0, 20) + def test_offsets(self, style_schema_path): config = {"customization": {"layout": { "title_text": {"x_offset": 4, "y_offset": -2}}}}