"""Tests for the streamlit-drawable-canvas compatibility shim. The shim re-attaches ``image_to_url`` to ``streamlit.elements.image`` on modern Streamlit where the helper was relocated to ``streamlit.elements.lib.image_utils`` and given a new signature (takes a ``LayoutConfig`` dataclass instead of a plain ``int`` width). If this test ever fails on a Streamlit upgrade, it almost certainly means the ``image_to_url`` function moved AGAIN — the shim's fallback message points to where to look. Update ``_drawable_canvas_compat.py`` to find the new location. """ from __future__ import annotations import sys import types def test_shim_attaches_image_to_url(): """After ``install()`` the old import path resolves to a callable, even on modern Streamlit where the original was relocated.""" # Force a fresh import so the module-level _PATCHED guard # doesn't short-circuit between tests. sys.modules.pop("src.gui._drawable_canvas_compat", None) from src.gui._drawable_canvas_compat import install install() import streamlit.elements.image as old_loc assert hasattr(old_loc, "image_to_url") assert callable(old_loc.image_to_url) def test_shim_is_idempotent(): """Calling ``install()`` twice doesn't double-wrap or break anything — important because the page module imports + calls it once, and a Streamlit script-rerun re-executes the page module top-to-bottom.""" sys.modules.pop("src.gui._drawable_canvas_compat", None) from src.gui._drawable_canvas_compat import install install() import streamlit.elements.image as old_loc first = old_loc.image_to_url install() second = old_loc.image_to_url assert first is second def test_shim_no_op_when_image_to_url_already_present(): """If a future Streamlit restores ``image_to_url`` at the old location, the shim must not overwrite it — leave the upstream function in place so the canvas package gets the official version, not our compatibility wrapper.""" sys.modules.pop("src.gui._drawable_canvas_compat", None) import streamlit.elements.image as old_loc sentinel = lambda *a, **kw: "sentinel-url" # noqa: E731 old_loc.image_to_url = sentinel try: from src.gui._drawable_canvas_compat import install install() assert old_loc.image_to_url is sentinel, ( "Shim must not clobber an existing image_to_url." ) finally: # Tidy up so subsequent tests see a clean module. delattr(old_loc, "image_to_url") sys.modules.pop("src.gui._drawable_canvas_compat", None) def test_shim_calls_new_function_with_layout_config(): """The shim's wrapper must translate the old ``(image, width, clamp, channels, output_format, image_id)`` call into the new ``(image, layout_config, …)`` signature without breaking.""" sys.modules.pop("src.gui._drawable_canvas_compat", None) import streamlit.elements.image as old_loc if hasattr(old_loc, "image_to_url"): delattr(old_loc, "image_to_url") # Replace the new function with a recorder so we can inspect # what arguments the shim passed through. from streamlit.elements.lib import image_utils captured: dict = {} original = image_utils.image_to_url def recorder(image, layout_config, clamp, channels, output_format, image_id): captured["image"] = image captured["layout_config"] = layout_config captured["clamp"] = clamp captured["channels"] = channels captured["output_format"] = output_format captured["image_id"] = image_id return "fake-url" image_utils.image_to_url = recorder try: from src.gui._drawable_canvas_compat import install install() result = old_loc.image_to_url( "fake-image", -1, False, "RGB", "PNG", "test-id", ) assert result == "fake-url" assert captured["image"] == "fake-image" assert captured["clamp"] is False assert captured["channels"] == "RGB" assert captured["output_format"] == "PNG" assert captured["image_id"] == "test-id" # The shim wraps the int width into a LayoutConfig. from streamlit.elements.lib.layout_utils import LayoutConfig assert isinstance(captured["layout_config"], LayoutConfig) finally: image_utils.image_to_url = original if hasattr(old_loc, "image_to_url"): delattr(old_loc, "image_to_url") sys.modules.pop("src.gui._drawable_canvas_compat", None)