mirror of
https://github.com/ChuckBuilds/LEDMatrix.git
synced 2026-10-14 02:56:36 +00:00
refactor(sports): one reader for element colours and layout offsets
There were two copies of the per-element colour read and three of the layout-offset read. They had already drifted -- the scroll-card renderer carries a comment about having ignored offsets its own schema advertised -- and each new capability had to be added to all of them or silently work in some places and not others. All of them now go through src.element_style, which is what carries the alias handling and the per-mode lookup. That lands immediately for the nine plugins importing these modules: a scoreboard asking for `score_text` offsets finds the `layout.score` its users configured, and a Live instance resolves its own colours through SKIN_MODE without any call site passing a mode. _normalize_color learned "#RRGGBB" in the process. The scoreboards' own readers have always accepted it, so the shared one had to, or consolidating would have quietly dropped a form users' configs may hold. _coerce_offset picked up the non-finite guard the scroll-card reader had and the other two did not. _get_layout_offset is promoted onto SportsCoreSharedMixin. Each plugin still carries its own copy in its bundled sports.py, which wins by MRO -- so adopting this is a deletion in the plugin, and until that deletion nothing changes for it. Note for whoever runs the suite next: test_display_dirty_tracking.py is order-dependent. Fifteen of its tests failed in one full run and passed in the next with no change in between, and pass in isolation. Pre-existing, unrelated to this, but it makes a full-run diff untrustworthy until it is fixed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -262,3 +262,53 @@ class TestUnshareElementFonts:
|
||||
fonts = {"score": a, "time": b}
|
||||
C.unshare_element_fonts(log, fonts)
|
||||
assert fonts["score"] is a and fonts["time"] is b
|
||||
|
||||
class TestSharedElementReaders:
|
||||
"""The scoreboards' colour and offset reads now go through one
|
||||
implementation.
|
||||
|
||||
There were two copies of the colour read and three of the offset read.
|
||||
They had already drifted: the scroll-card renderer carries a comment
|
||||
noting it used to ignore offsets its own schema advertised. Sharing one
|
||||
reader is what lets a fix (or an alias, or a per-mode override) reach
|
||||
the full-screen scorebug and the scroll card together.
|
||||
"""
|
||||
|
||||
CONFIG = {
|
||||
"customization": {
|
||||
"score_text": {"text_color": [255, 200, 0]},
|
||||
"status": {"text_color": "#00ff00"},
|
||||
"layout": {"score": {"y_offset": -3}},
|
||||
"modes": {
|
||||
"live": {"score_text": {"text_color": [255, 0, 0]},
|
||||
"layout": {"score": {"y_offset": 7}}},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
def test_colour_reads_the_configured_value(self, log):
|
||||
assert C.element_color(self.CONFIG, "score_text") == (255, 200, 0)
|
||||
|
||||
def test_hex_colours_are_still_accepted(self, log):
|
||||
"""These readers have always taken '#RRGGBB' as well as [r, g, b],
|
||||
so the shared one had to learn it rather than the callers losing it."""
|
||||
assert C.element_color(self.CONFIG, "status") == (0, 255, 0)
|
||||
|
||||
def test_colour_resolves_through_an_alias(self, log):
|
||||
"""The style block says status_text where this config says status."""
|
||||
assert C.element_color(self.CONFIG, "status_text") == (0, 255, 0)
|
||||
|
||||
def test_a_mode_overrides_the_colour(self, log):
|
||||
assert C.element_color(self.CONFIG, "score_text",
|
||||
mode="live") == (255, 0, 0)
|
||||
|
||||
def test_an_unset_element_gets_the_default(self, log):
|
||||
assert C.element_color(self.CONFIG, "nothing", (1, 2, 3)) == (1, 2, 3)
|
||||
|
||||
@pytest.mark.parametrize("config", [
|
||||
None, {}, "nonsense", {"customization": "nonsense"},
|
||||
{"customization": {"score_text": "nonsense"}},
|
||||
{"customization": {"score_text": {"text_color": "not a colour"}}},
|
||||
])
|
||||
def test_a_hostile_config_gives_the_default(self, config, log):
|
||||
assert C.element_color(config, "score_text", (9, 9, 9)) == (9, 9, 9)
|
||||
|
||||
@@ -256,3 +256,48 @@ class TestContract:
|
||||
h._draw_upcoming_center(_draw(), {})
|
||||
h._draw_upcoming_game_status(_draw(), {})
|
||||
h.set_rankings_cache({})
|
||||
|
||||
|
||||
class TestLayoutOffsetsAreShared:
|
||||
"""The scroll/Vegas card reads offsets through the same function the
|
||||
full-screen scorebug does.
|
||||
|
||||
This renderer already carries a comment about having ignored offsets its
|
||||
own schema advertised -- it was the third copy of this read. Sharing one
|
||||
means the alias handling and per-mode overrides land here too, without
|
||||
this file knowing about either.
|
||||
"""
|
||||
|
||||
CONFIG = {"customization": {
|
||||
"layout": {"score": {"y_offset": -3}},
|
||||
"modes": {"live": {"layout": {"score": {"y_offset": 7}}}},
|
||||
}}
|
||||
|
||||
def _host(self, config, mode=None):
|
||||
attrs = {"config": config}
|
||||
if mode:
|
||||
attrs["SKIN_MODE"] = mode
|
||||
return type("Card", (SportsGameRendererMixin,), attrs)()
|
||||
|
||||
def test_it_reads_the_configured_offset(self):
|
||||
assert self._host(self.CONFIG)._layout_offset("score", "y_offset") == -3
|
||||
|
||||
def test_it_resolves_through_an_alias(self):
|
||||
"""A caller asking for score_text finds the layout.score the user
|
||||
configured -- the two namespaces disagree by convention."""
|
||||
assert self._host(self.CONFIG)._layout_offset(
|
||||
"score_text", "y_offset") == -3
|
||||
|
||||
def test_skin_mode_selects_the_per_mode_offset(self):
|
||||
assert self._host(self.CONFIG, "live")._layout_offset(
|
||||
"score", "y_offset") == 7
|
||||
|
||||
def test_a_host_without_a_skin_mode_gets_the_base(self):
|
||||
assert self._host(self.CONFIG)._layout_offset("score", "y_offset") == -3
|
||||
|
||||
@pytest.mark.parametrize("written", [
|
||||
float("inf"), float("nan"), True, "bad", None, [], {},
|
||||
])
|
||||
def test_nonsense_degrades_to_the_default(self, written):
|
||||
config = {"customization": {"layout": {"score": {"y_offset": written}}}}
|
||||
assert self._host(config)._layout_offset("score", "y_offset", 4) == 4
|
||||
|
||||
@@ -422,3 +422,43 @@ class TestUnshareKeepsTheLayoutEngine:
|
||||
fonts = {"score": shared, "time": shared}
|
||||
self._host()._unshare_element_fonts(fonts)
|
||||
assert fonts["time"].getlength("88-88") == shared.getlength("88-88")
|
||||
|
||||
|
||||
class TestPromotedLayoutOffset:
|
||||
"""_get_layout_offset moved here so every scoreboard reads offsets the
|
||||
way the scroll card does.
|
||||
|
||||
Each plugin still carries its own copy in its bundled sports.py, which
|
||||
wins by MRO. That is the migration property: adopting this is a
|
||||
deletion in the plugin, and until that deletion nothing changes.
|
||||
"""
|
||||
|
||||
CONFIG = {"customization": {
|
||||
"layout": {"score": {"y_offset": -3}},
|
||||
"modes": {"recent": {"layout": {"score": {"y_offset": 9}}}},
|
||||
}}
|
||||
|
||||
def _host(self, **attrs):
|
||||
return type("H", (SportsCoreSharedMixin,),
|
||||
dict({"config": self.CONFIG}, **attrs))()
|
||||
|
||||
def test_it_reads_the_configured_offset(self):
|
||||
assert self._host()._get_layout_offset("score", "y_offset") == -3
|
||||
|
||||
def test_it_resolves_through_an_alias(self):
|
||||
"""What a plugin gains by deleting its own copy: the style block
|
||||
says score_text where the layout block says score."""
|
||||
assert self._host()._get_layout_offset("score_text", "y_offset") == -3
|
||||
|
||||
def test_skin_mode_selects_the_per_mode_offset(self):
|
||||
assert self._host(SKIN_MODE="recent")._get_layout_offset(
|
||||
"score", "y_offset") == 9
|
||||
|
||||
def test_a_plugins_own_copy_still_wins(self):
|
||||
"""Until a plugin deletes its copy, this changes nothing for it."""
|
||||
host = self._host(
|
||||
_get_layout_offset=lambda self, element, axis, default=0: 99)
|
||||
assert host._get_layout_offset("score", "y_offset") == 99
|
||||
|
||||
def test_an_unset_offset_is_the_default(self):
|
||||
assert self._host()._get_layout_offset("nothing", "y_offset", 5) == 5
|
||||
|
||||
Reference in New Issue
Block a user