Files
LEDMatrix/test/test_scroll_helper_image_entry.py
ChuckandClaude Opus 5.5 7eb7a58d0c fix: web UI and src.common bugs (wifi wrong-password, plugin icon, starlark toggle, API caching, scroll/logo/font helpers) (#646)
- wifi: keep the "wrong_password:" prefix through the restore/AP fallback so
  the UI's incorrect-password prompt fires again.
- /plugins/installed returns the manifest's icon (string only).
- /starlark/apps/<id>/toggle coerces `enabled` and delegates to
  _toggle_starlark_app (disk before memory, no KeyError, "false" is false).
- /api/v3/ JSON GETs are sent Cache-Control: no-store; non-JSON keeps 5s.
- ScrollHelper.set_scrolling_image converts non-RGB input (alpha onto black);
  create/set_scrolling_image reset last_update_time like reset_scroll.
- LogoHelper backs off a failed download per path for
  MISSING_LOGO_RECHECK_SECONDS; cleared on invalidate/clear_cache.
- refresh_placeholder_timestamp saves atomically.
- FontManager.clear_cache / _clear_plugin_font_cache bump cache_generation.
- Odds manager: per-game logs to DEBUG; JSON decode error caught before
  RequestException (same cooldown).
- element_style mangled continuations; startup validator skips null plugin
  blocks and reuses the controller's discovery.
- src/common/README lists frame_timing, json_body, render_gate.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 08:26:05 -04:00

88 lines
3.3 KiB
Python

"""ScrollHelper: what a new scrolling image does on the way in.
- set_scrolling_image cached a non-RGB image as-is, and every frame is cut
with Image.frombytes('RGB', ...): an RGBA strip came out as garbage and an
L strip raised.
- Only reset_scroll() reset last_update_time. In time-based mode the first
update after a new image (e.g. the plugin coming back on screen) advanced
the position by the whole idle gap, skipping the start of the content.
"""
import time
import pytest
from PIL import Image
from src.common.scroll_helper import ScrollHelper
W, H = 16, 8
@pytest.fixture
def helper():
return ScrollHelper(display_width=W, display_height=H)
class TestNonRgbImages:
def test_rgba_is_composited_onto_black(self, helper):
img = Image.new('RGBA', (40, H), (0, 0, 0, 0))
for x in range(10):
for y in range(H):
img.putpixel((x, y), (255, 0, 0, 255))
# Hidden colour under full transparency must not leak through.
img.putpixel((20, 0), (0, 255, 0, 0))
helper.set_scrolling_image(img)
assert helper.cached_image.mode == 'RGB'
frame = helper.get_visible_portion()
assert frame.size == (W, H)
assert frame.getpixel((0, 0)) == (255, 0, 0)
assert frame.getpixel((12, 0)) == (0, 0, 0)
helper.scroll_position = 20.0
assert helper.get_visible_portion().getpixel((0, 0)) == (0, 0, 0)
def test_greyscale_does_not_raise(self, helper):
helper.set_scrolling_image(Image.new('L', (40, H), 200))
frame = helper.get_visible_portion()
assert frame.getpixel((0, 0)) == (200, 200, 200)
def test_palette_with_transparency(self, helper):
img = Image.new('P', (40, H), 1)
img.putpalette([0, 0, 0, 0, 0, 255] + [0] * 762)
img.info['transparency'] = 1
helper.set_scrolling_image(img)
assert helper.get_visible_portion().getpixel((0, 0)) == (0, 0, 0)
def test_an_rgb_image_is_kept_as_the_same_object(self, helper):
img = Image.new('RGB', (40, H), (1, 2, 3))
helper.set_scrolling_image(img)
assert helper.cached_image is img
class TestNoJumpAfterIdle:
def _idle_then_new_image(self, helper, install):
helper.scroll_speed = 100.0 # px/s, time-based
install(helper)
helper.update_scroll_position()
# Off screen for 10 seconds, then a fresh image.
helper.last_update_time = time.time() - 10.0
install(helper)
helper.update_scroll_position()
return helper.scroll_position
def test_set_scrolling_image(self, helper):
pos = self._idle_then_new_image(
helper, lambda h: h.set_scrolling_image(Image.new('RGB', (5000, H))))
assert pos < 50, f"jumped {pos}px on the first update of a new image"
def test_create_scrolling_image(self, helper):
pos = self._idle_then_new_image(
helper, lambda h: h.create_scrolling_image([Image.new('RGB', (5000, H))]))
assert pos < 50, f"jumped {pos}px on the first update of a new image"
def test_fixed_step_mode_still_steps_exactly(self, helper):
helper.set_pixels_per_frame(2)
helper.set_scrolling_image(Image.new('RGB', (5000, H)))
helper.update_scroll_position()
helper.update_scroll_position()
assert helper.scroll_position == 4.0