from __future__ import annotations import pytest from PIL import Image from somafm_tui import artwork from somafm_tui.app import SomaFMApp from somafm_tui.widgets.art import ArtPane, ITerm2Image, KittyImage from somafm_tui.widgets.stage import StagePanel async def _no_fetch(client, url: str) -> None: return None @pytest.mark.asyncio async def test_art_pane_switches_between_image_and_mosaic(monkeypatch) -> None: monkeypatch.setattr(artwork, "PROTOCOL", "tgp") monkeypatch.setattr( artwork, "fit_image", lambda data, width, height: Image.new( "RGBA", (width * 10, height * 20), (10, 20, 30, 255) ), ) monkeypatch.setattr(artwork, "load_cached", lambda url: None) monkeypatch.setattr(artwork, "fetch", _no_fetch) app = SomaFMApp(autoplay=False, offline=True, audio=False) async with app.run_test(size=(110, 40)) as pilot: await pilot.pause() pane = app.query_one(ArtPane) panel = app.query_one(StagePanel) app.artwork_bytes = b"fake-image-bytes" panel.refresh_art() await pilot.pause() assert isinstance(pane._image_widget, KittyImage) app.artwork_bytes = None panel.refresh_art() await pilot.pause() assert pane._image_widget is None # The mosaic is always painted by ArtPane.render(), even under images. assert pane.render() is not None def test_iterm2_sequence_uses_cell_units() -> None: widget = ITerm2Image(Image.new("RGBA", (32, 32), (10, 20, 30, 255))) sequence = widget._sequence(13, 8) assert sequence.startswith( "\x1b]1337;File=inline=1;width=13;height=8;" ) def test_kitty_direct_placement_sequences() -> None: widget = KittyImage(Image.new("RGBA", (32, 32), (10, 20, 30, 255))) commands = widget._sequences(13, 8) joined = "".join(commands) assert "a=t" in joined assert "a=p" in joined assert "c=13,r=8" in joined assert "U=1" not in joined # no Unicode placeholders assert widget._sequences(13, 8) == [] widget.cleanup()