mirror of
https://github.com/ChuckBuilds/LEDMatrix.git
synced 2026-05-24 13:23:32 +00:00
Adds test coverage for six major untested areas: - src/base_classes/api_extractors.py — ESPN football, baseball, hockey, soccer extractors - src/base_classes/data_sources.py — ESPN, MLB, and soccer API data sources (HTTP mocked) - src/common/game_helper.py — game extraction, filtering, sorting, and summaries - src/common/utils.py — all utility functions (normalise, format, validate, parse) - src/common/scroll_helper.py — ScrollHelper init, create, update, visible portion, duration - src/background_data_service.py — cache hit/miss paths, retry, cancel, cleanup, singleton - src/vegas_mode/config.py — VegasModeConfig from_config, validate, update, ordering - src/logo_downloader.py — normalize_abbreviation, filename variations, directory helpers - src/plugin_system/health_monitor.py — HealthStatus determination, metrics, suggestions, lifecycle https://claude.ai/code/session_015792DiGo27JbgH5mk3KBjk
318 lines
12 KiB
Python
318 lines
12 KiB
Python
"""
|
|
Tests for src/common/scroll_helper.py
|
|
|
|
Covers ScrollHelper: create_scrolling_image, update_scroll_position,
|
|
get_visible_portion, calculate_dynamic_duration, set_* methods,
|
|
reset_scroll, clear_cache, get_scroll_info.
|
|
"""
|
|
|
|
import pytest
|
|
import time
|
|
from unittest.mock import patch
|
|
from PIL import Image
|
|
|
|
from src.common.scroll_helper import ScrollHelper
|
|
|
|
|
|
DISPLAY_W = 64
|
|
DISPLAY_H = 32
|
|
|
|
|
|
@pytest.fixture
|
|
def helper():
|
|
return ScrollHelper(display_width=DISPLAY_W, display_height=DISPLAY_H)
|
|
|
|
|
|
def _make_image(width: int = 64, height: int = 32, color=(255, 0, 0)) -> Image.Image:
|
|
img = Image.new("RGB", (width, height), color)
|
|
return img
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# __init__ / initial state
|
|
# ---------------------------------------------------------------------------
|
|
|
|
class TestScrollHelperInit:
|
|
def test_initial_scroll_position(self, helper):
|
|
assert helper.scroll_position == 0.0
|
|
|
|
def test_initial_scroll_complete_false(self, helper):
|
|
assert helper.scroll_complete is False
|
|
|
|
def test_display_dimensions(self, helper):
|
|
assert helper.display_width == DISPLAY_W
|
|
assert helper.display_height == DISPLAY_H
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# create_scrolling_image
|
|
# ---------------------------------------------------------------------------
|
|
|
|
class TestCreateScrollingImage:
|
|
def test_empty_content_returns_blank_image(self, helper):
|
|
result = helper.create_scrolling_image([])
|
|
assert isinstance(result, Image.Image)
|
|
assert helper.total_scroll_width == 0
|
|
|
|
def test_single_item_creates_image(self, helper):
|
|
img = _make_image(width=100)
|
|
result = helper.create_scrolling_image([img])
|
|
assert isinstance(result, Image.Image)
|
|
assert result.width > DISPLAY_W # includes leading gap
|
|
|
|
def test_multiple_items_wider_image(self, helper):
|
|
items = [_make_image(width=50), _make_image(width=50)]
|
|
result = helper.create_scrolling_image(items)
|
|
# Should be wider than two items alone
|
|
assert result.width > 100
|
|
|
|
def test_scroll_position_reset(self, helper):
|
|
helper.scroll_position = 500.0
|
|
helper.create_scrolling_image([_make_image()])
|
|
assert helper.scroll_position == 0.0
|
|
|
|
def test_cached_array_set(self, helper):
|
|
helper.create_scrolling_image([_make_image()])
|
|
assert helper.cached_array is not None
|
|
|
|
def test_scroll_complete_reset(self, helper):
|
|
helper.scroll_complete = True
|
|
helper.create_scrolling_image([_make_image()])
|
|
assert helper.scroll_complete is False
|
|
|
|
def test_total_scroll_width_matches_image(self, helper):
|
|
img = _make_image(width=200)
|
|
result = helper.create_scrolling_image([img])
|
|
assert helper.total_scroll_width == result.width
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# set_scrolling_image
|
|
# ---------------------------------------------------------------------------
|
|
|
|
class TestSetScrollingImage:
|
|
def test_sets_cached_image(self, helper):
|
|
img = _make_image(width=200)
|
|
helper.set_scrolling_image(img)
|
|
assert helper.cached_image is img
|
|
|
|
def test_sets_cached_array(self, helper):
|
|
img = _make_image(width=200)
|
|
helper.set_scrolling_image(img)
|
|
assert helper.cached_array is not None
|
|
|
|
def test_scroll_width_matches_image(self, helper):
|
|
img = _make_image(width=300)
|
|
helper.set_scrolling_image(img)
|
|
assert helper.total_scroll_width == 300
|
|
|
|
def test_none_clears_cache(self, helper):
|
|
helper.set_scrolling_image(_make_image())
|
|
helper.set_scrolling_image(None)
|
|
assert helper.cached_image is None
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# update_scroll_position (time-based mode)
|
|
# ---------------------------------------------------------------------------
|
|
|
|
class TestUpdateScrollPosition:
|
|
def test_position_advances_over_time(self, helper):
|
|
helper.create_scrolling_image([_make_image(width=200)])
|
|
helper.scroll_speed = 100.0 # 100 px/s
|
|
helper.last_update_time = time.time() - 0.1 # pretend 100ms elapsed
|
|
initial = helper.scroll_position
|
|
helper.update_scroll_position()
|
|
assert helper.scroll_position > initial
|
|
|
|
def test_no_advance_without_image(self, helper):
|
|
helper.update_scroll_position() # no image, should not crash
|
|
assert helper.scroll_position == 0.0
|
|
|
|
def test_zero_width_content_stays_zero(self, helper):
|
|
helper.create_scrolling_image([]) # empty → width 0
|
|
helper.update_scroll_position()
|
|
assert helper.scroll_position == 0.0
|
|
|
|
def test_scroll_complete_clamped(self, helper):
|
|
helper.create_scrolling_image([_make_image(width=100)])
|
|
# Force position past the end
|
|
helper.scroll_position = helper.total_scroll_width + 50
|
|
helper.total_distance_scrolled = helper.total_scroll_width + 50
|
|
helper.update_scroll_position()
|
|
assert helper.scroll_complete is True
|
|
assert helper.scroll_position <= helper.total_scroll_width
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# get_visible_portion
|
|
# ---------------------------------------------------------------------------
|
|
|
|
class TestGetVisiblePortion:
|
|
def test_returns_none_without_image(self, helper):
|
|
assert helper.get_visible_portion() is None
|
|
|
|
def test_returns_image_sized_to_display(self, helper):
|
|
helper.create_scrolling_image([_make_image(width=200)])
|
|
visible = helper.get_visible_portion()
|
|
assert visible is not None
|
|
assert visible.width == DISPLAY_W
|
|
assert visible.height == DISPLAY_H
|
|
|
|
def test_different_positions_give_different_images(self, helper):
|
|
helper.create_scrolling_image([_make_image(width=300)])
|
|
img1 = helper.get_visible_portion()
|
|
helper.scroll_position = 50
|
|
img2 = helper.get_visible_portion()
|
|
# Images should differ (colour from scrolled content)
|
|
# Just verify both are valid PIL images with correct size
|
|
assert img1.width == img2.width == DISPLAY_W
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# reset_scroll / clear_cache
|
|
# ---------------------------------------------------------------------------
|
|
|
|
class TestResetAndClear:
|
|
def test_reset_restores_position(self, helper):
|
|
helper.create_scrolling_image([_make_image(width=200)])
|
|
helper.scroll_position = 100.0
|
|
helper.reset_scroll()
|
|
assert helper.scroll_position == 0.0
|
|
|
|
def test_reset_clears_complete_flag(self, helper):
|
|
helper.scroll_complete = True
|
|
helper.reset_scroll()
|
|
assert helper.scroll_complete is False
|
|
|
|
def test_reset_alias(self, helper):
|
|
helper.scroll_position = 50.0
|
|
helper.reset()
|
|
assert helper.scroll_position == 0.0
|
|
|
|
def test_clear_cache(self, helper):
|
|
helper.create_scrolling_image([_make_image()])
|
|
helper.clear_cache()
|
|
assert helper.cached_image is None
|
|
assert helper.cached_array is None
|
|
assert helper.total_scroll_width == 0
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# calculate_dynamic_duration
|
|
# ---------------------------------------------------------------------------
|
|
|
|
class TestCalculateDynamicDuration:
|
|
def test_returns_min_when_disabled(self, helper):
|
|
helper.dynamic_duration_enabled = False
|
|
helper.min_duration = 30
|
|
result = helper.calculate_dynamic_duration()
|
|
assert result == 30
|
|
|
|
def test_returns_min_when_no_content(self, helper):
|
|
helper.total_scroll_width = 0
|
|
helper.min_duration = 30
|
|
result = helper.calculate_dynamic_duration()
|
|
assert result == 30
|
|
|
|
def test_respects_min_duration(self, helper):
|
|
helper.create_scrolling_image([_make_image(width=50)])
|
|
helper.min_duration = 60
|
|
helper.max_duration = 300
|
|
helper.scroll_speed = 500.0 # very fast → very short time
|
|
result = helper.calculate_dynamic_duration()
|
|
assert result >= 60
|
|
|
|
def test_respects_max_duration(self, helper):
|
|
helper.create_scrolling_image([_make_image(width=50000)])
|
|
helper.min_duration = 10
|
|
helper.max_duration = 60
|
|
helper.scroll_speed = 1.0 # very slow → very long time
|
|
result = helper.calculate_dynamic_duration()
|
|
assert result <= 60
|
|
|
|
def test_time_based_calculation(self, helper):
|
|
helper.create_scrolling_image([_make_image(width=200)])
|
|
helper.scroll_speed = 100.0
|
|
helper.min_duration = 1
|
|
helper.max_duration = 600
|
|
helper.frame_based_scrolling = False
|
|
result = helper.calculate_dynamic_duration()
|
|
assert isinstance(result, int)
|
|
assert result > 0
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# set_* configuration methods
|
|
# ---------------------------------------------------------------------------
|
|
|
|
class TestSetMethods:
|
|
def test_set_scroll_speed_time_based(self, helper):
|
|
helper.frame_based_scrolling = False
|
|
helper.set_scroll_speed(50.0)
|
|
assert helper.scroll_speed == 50.0
|
|
|
|
def test_set_scroll_speed_clamped_low(self, helper):
|
|
helper.frame_based_scrolling = False
|
|
helper.set_scroll_speed(0.0)
|
|
assert helper.scroll_speed >= 1.0
|
|
|
|
def test_set_scroll_speed_clamped_high(self, helper):
|
|
helper.frame_based_scrolling = False
|
|
helper.set_scroll_speed(10000.0)
|
|
assert helper.scroll_speed <= 500.0
|
|
|
|
def test_set_scroll_delay(self, helper):
|
|
helper.set_scroll_delay(0.05)
|
|
assert helper.scroll_delay == 0.05
|
|
|
|
def test_set_scroll_delay_clamped(self, helper):
|
|
helper.set_scroll_delay(0.0001)
|
|
assert helper.scroll_delay >= 0.001
|
|
|
|
def test_set_target_fps(self, helper):
|
|
helper.set_target_fps(60.0)
|
|
assert helper.target_fps == 60.0
|
|
|
|
def test_set_target_fps_clamped(self, helper):
|
|
helper.set_target_fps(1000.0)
|
|
assert helper.target_fps <= 200.0
|
|
|
|
def test_set_sub_pixel_scrolling(self, helper):
|
|
helper.set_sub_pixel_scrolling(True)
|
|
assert helper.sub_pixel_scrolling is True
|
|
helper.set_sub_pixel_scrolling(False)
|
|
assert helper.sub_pixel_scrolling is False
|
|
|
|
def test_set_frame_based_scrolling(self, helper):
|
|
helper.set_frame_based_scrolling(True)
|
|
assert helper.frame_based_scrolling is True
|
|
|
|
def test_set_dynamic_duration_settings(self, helper):
|
|
helper.set_dynamic_duration_settings(enabled=True, min_duration=20, max_duration=120, buffer=0.2)
|
|
assert helper.dynamic_duration_enabled is True
|
|
assert helper.min_duration == 20
|
|
assert helper.max_duration == 120
|
|
assert helper.duration_buffer == pytest.approx(0.2)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# get_scroll_info
|
|
# ---------------------------------------------------------------------------
|
|
|
|
class TestGetScrollInfo:
|
|
def test_returns_dict(self, helper):
|
|
info = helper.get_scroll_info()
|
|
assert isinstance(info, dict)
|
|
|
|
def test_required_keys(self, helper):
|
|
info = helper.get_scroll_info()
|
|
for key in ("scroll_position", "total_distance_scrolled", "scroll_speed",
|
|
"scroll_complete", "dynamic_duration"):
|
|
assert key in info
|
|
|
|
def test_scroll_position_reflected(self, helper):
|
|
helper.scroll_position = 42.0
|
|
info = helper.get_scroll_info()
|
|
assert info["scroll_position"] == 42.0
|