Files
beefy-player/tests/test_art.py
T
beefcarver e30ca09627 refactor!: rename somafm-tui to beefy-player
The app is now a general internet-radio player rather than a SomaFM client, so the package, console script, MPRIS identity, mpv namespaces, macOS tap names, XDG directories and docs all move to beefy-player.

Environment variables are now BEEFY_* (the old SOMAFM_* spellings are still honoured) and an existing ~/.config/somafm-tui/config.json is migrated on first run, so favourites and volume survive the rename.

The visible interface is untouched: same layout, same logo, same keys.
2026-11-03 16:10:25 -05:00

78 lines
2.7 KiB
Python

from __future__ import annotations
import pytest
from PIL import Image
from beefy_player import artwork
from beefy_player.app import BeefyPlayerApp
from beefy_player.widgets import art as art_widgets
from beefy_player.widgets.art import ArtPane, ITerm2Image, KittyImage
from beefy_player.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 = BeefyPlayerApp(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(monkeypatch) -> None:
monkeypatch.setattr(art_widgets, "_iterm2_native", lambda: False)
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;"
)
# WezTerm shares the protocol but not every iTerm2-only parameter.
assert "doNotMoveCursor" not in sequence
def test_iterm2_native_keeps_the_cursor_put(monkeypatch) -> None:
monkeypatch.setattr(art_widgets, "_iterm2_native", lambda: True)
widget = ITerm2Image(Image.new("RGBA", (32, 32), (10, 20, 30, 255)))
sequence = widget._sequence(13, 8)
assert sequence.startswith("\x1b]1337;File=inline=1;doNotMoveCursor=1;")
assert "width=13;height=8;" in sequence
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()