"""app.widgets.static_image -- unit-level, no HTTP: constructs Widget/ StaticWidgetConfig rows directly. The decode-at-upload-time path (see app/image_upload.py) is covered separately in test_image_upload.py and the HTTP-level upload endpoint in test_widget_config_and_queue_endpoints.py -- these tests only exercise render(), which always starts from already-decoded PNG bytes.""" from __future__ import annotations import io import time from PIL import Image from app import widgets from app.models import Frame, StaticWidgetConfig, Widget def _png_bytes(size=(40, 30), color=(200, 50, 50)) -> bytes: buf = io.BytesIO() Image.new("RGB", size, color).save(buf, format="PNG") return buf.getvalue() def _make_widget(db_session, **cfg_kwargs) -> tuple[Frame, Widget]: frame = db_session.get(Frame, 1) widget = Widget(frame_id=frame.id, widget_type="static", x=0, y=0, w=2, h=2, sort_order=0, created_at=time.time()) db_session.add(widget) db_session.flush() db_session.add(StaticWidgetConfig(widget_id=widget.id, **cfg_kwargs)) db_session.commit() return frame, widget def test_render_shows_a_placeholder_when_no_image_uploaded(db_session): frame, widget = _make_widget(db_session) img = widgets.static_image.render(db_session, frame, widget, 300, 200) assert img.size == (300, 200) assert img.mode == "RGB" def test_render_composes_the_uploaded_image(db_session): frame, widget = _make_widget(db_session, image=_png_bytes()) img = widgets.static_image.render(db_session, frame, widget, 300, 200) assert img.size == (300, 200) assert img.mode == "RGB" def test_render_respects_display_mode_stretch_fill(db_session): frame, widget = _make_widget(db_session, image=_png_bytes(), display_mode="stretch_fill") img = widgets.static_image.render(db_session, frame, widget, 300, 200) assert img.size == (300, 200) def test_render_respects_display_mode_letterbox(db_session): frame, widget = _make_widget(db_session, image=_png_bytes(size=(100, 10)), display_mode="letterbox") img = widgets.static_image.render(db_session, frame, widget, 300, 200) assert img.size == (300, 200) def test_render_at_minimum_grid_footprint(db_session): """grid.MIN_FOOTPRINT["static"] is (1, 1) cells -- on an 8x5 grid against a full 800x480 panel that's a 100x96 box, the smallest a static widget can actually be placed at.""" frame, widget = _make_widget(db_session, image=_png_bytes()) img = widgets.static_image.render(db_session, frame, widget, 100, 96) assert img.size == (100, 96) def test_no_button_actions(): """A fixed uploaded image -- nothing to advance/back/check.""" assert widgets.static_image.ACTIONS == {} assert widgets.static_image.ACTION_LABELS == {} def test_render_modern_style(db_session, monkeypatch): from app import html_render frame, widget = _make_widget(db_session, image=_png_bytes(), render_style="modern") def _stub(html, target_w, target_h): return Image.new("RGB", (target_w, target_h), (255, 255, 255)) monkeypatch.setattr(html_render, "render_html_to_image", _stub) img = widgets.static_image.render(db_session, frame, widget, 300, 200) assert img.size == (300, 200) assert img.mode == "RGB"