"""app.widgets.whiteboard -- unit-level, no HTTP: constructs Widget/ WhiteboardWidgetConfig rows directly and monkeypatches the underlying fetch/render call (get_or_refresh_whiteboard_for_widget, already covered against a real WebDAV server + mocked sidecar in test_whiteboard_refresh_and_browse.py) rather than re-testing that throttle/fetch logic here.""" from __future__ import annotations import io import time from PIL import Image from app import widgets from app.models import Frame, Widget, WhiteboardWidgetConfig def _make_widget(db_session, render_style="classic") -> tuple[Frame, Widget]: frame = db_session.get(Frame, 1) widget = Widget(frame_id=frame.id, widget_type="whiteboard", x=0, y=0, w=8, h=5, sort_order=0, created_at=time.time()) db_session.add(widget) db_session.flush() db_session.add(WhiteboardWidgetConfig(widget_id=widget.id, url="https://example.com/board.whiteboard", render_style=render_style)) db_session.commit() return frame, widget def _tiny_png() -> bytes: buf = io.BytesIO() Image.new("RGB", (40, 20), (10, 20, 30)).save(buf, format="PNG") return buf.getvalue() def test_render_unconfigured_widget_returns_correctly_sized_placeholder(db_session): frame = db_session.get(Frame, 1) widget = Widget(frame_id=frame.id, widget_type="whiteboard", x=0, y=0, w=8, h=5, sort_order=0, created_at=time.time()) db_session.add(widget) db_session.flush() db_session.add(WhiteboardWidgetConfig(widget_id=widget.id)) # no url/user_id set db_session.commit() img = widgets.whiteboard.render(db_session, frame, widget, 300, 200) assert img.size == (300, 200) def test_render_configured_widget_composes_the_fetched_png(db_session, monkeypatch): frame, widget = _make_widget(db_session) png = _tiny_png() monkeypatch.setattr(widgets.whiteboard, "get_or_refresh_whiteboard_for_widget", lambda db, frame, widget, force=False: png) img = widgets.whiteboard.render(db_session, frame, widget, 300, 200) assert img.size == (300, 200) assert img.mode == "RGB" def test_render_falls_back_to_placeholder_when_nothing_cached_yet(db_session, monkeypatch): frame, widget = _make_widget(db_session) monkeypatch.setattr(widgets.whiteboard, "get_or_refresh_whiteboard_for_widget", lambda db, frame, widget, force=False: None) img = widgets.whiteboard.render(db_session, frame, widget, 300, 200) assert img.size == (300, 200) def test_check_now_action_forces_a_refetch(db_session, monkeypatch): frame, widget = _make_widget(db_session) calls = [] def fake_refresh(db, frame, widget, force=False): calls.append(force) return _tiny_png() monkeypatch.setattr(widgets.whiteboard, "get_or_refresh_whiteboard_for_widget", fake_refresh) widgets.whiteboard.ACTIONS["check_now"](db_session, frame, widget) assert calls == [True] def test_both_buttons_map_to_check_now(): """No real "next"/"back" concept for a static board -- both physical buttons mean the same thing for a whiteboard widget.""" assert set(widgets.whiteboard.ACTIONS) == {"check_now"} def test_render_modern_style(db_session, monkeypatch): from app import html_render frame, widget = _make_widget(db_session, render_style="modern") monkeypatch.setattr(widgets.whiteboard, "get_or_refresh_whiteboard_for_widget", lambda db, frame, widget: _tiny_png()) 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.whiteboard.render(db_session, frame, widget, 300, 200) assert img.size == (300, 200) assert img.mode == "RGB"