Files
beefy-player/tests/test_artwork.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
3.4 KiB
Python

from __future__ import annotations
from io import BytesIO
import httpx
import pytest
from PIL import Image
from beefy_player import artwork
def png_bytes(size=(400, 200)) -> bytes:
buffer = BytesIO()
Image.new("RGBA", size, (20, 180, 220, 180)).save(buffer, format="PNG")
return buffer.getvalue()
def test_protocol_detection() -> None:
assert artwork.detect_protocol({"KITTY_WINDOW_ID": "1"}) == "tgp"
assert artwork.detect_protocol({"TERM": "xterm-kitty"}) == "tgp"
assert artwork.detect_protocol({"TERM_PROGRAM": "WezTerm"}) == "iterm2"
assert artwork.detect_protocol({"WEZTERM_EXECUTABLE": "/bin/wezterm"}) == "iterm2"
assert artwork.detect_protocol({"TERM_PROGRAM": "iTerm.app"}) == "iterm2"
assert artwork.detect_protocol({"ITERM_SESSION_ID": "x"}) == "iterm2"
assert artwork.detect_protocol({"TERM_PROGRAM": "Apple_Terminal"}) is None
assert artwork.detect_protocol({"TMUX": "/tmp/tmux", "KITTY_WINDOW_ID": "1"}) == "tgp"
assert artwork.detect_protocol({"TMUX": "/tmp/tmux", "TERM_PROGRAM": "WezTerm"}) is None
assert artwork.detect_protocol({"ZELLIJ": "1", "TERM_PROGRAM": "WezTerm"}) is None
assert artwork.detect_protocol({"KITTY_PID": "1"}) == "tgp"
assert artwork.detect_protocol({"NO_COLOR": "", "KITTY_WINDOW_ID": "1"}) is None
assert artwork.detect_protocol({"BEEFY_IMAGES": "0", "KITTY_WINDOW_ID": "1"}) is None
assert artwork.detect_protocol({"BEEFY_IMAGES": "tgp"}) == "tgp"
assert artwork.detect_protocol({"BEEFY_IMAGES": "iterm2"}) == "iterm2"
# the pre-rename variable still works
assert artwork.detect_protocol({"SOMAFM_IMAGES": "0"}) is None
def test_fit_image_crops_to_the_cell_box() -> None:
fitted = artwork.fit_image(png_bytes(), 13, 8)
assert fitted is not None
cell_width, cell_height = artwork.cell_size()
expected = (13 * cell_width) / (8 * cell_height)
assert fitted.width / fitted.height == pytest.approx(expected, rel=0.01)
assert artwork.fit_image(b"not an image", 13, 8) is None
def test_image_cache_roundtrip() -> None:
data = png_bytes()
url = "https://example.invalid/art.png"
artwork.save_cached(url, data)
assert artwork.load_cached(url) == data
assert artwork.load_cached("https://example.invalid/other.png") is None
@pytest.mark.asyncio
async def test_fetch_validates_and_caches() -> None:
data = png_bytes((120, 120))
url = "https://example.invalid/logo.png"
def handler(request: httpx.Request) -> httpx.Response:
return httpx.Response(200, content=data, headers={"content-type": "image/png"})
async with httpx.AsyncClient(transport=httpx.MockTransport(handler)) as client:
fetched = await artwork.fetch(client, url)
assert fetched == data
assert artwork.load_cached(url) == data
def test_multiplexer_env_values_that_mean_no() -> None:
# Some wrappers export ZELLIJ=0 / TMUX=0 outside a multiplexer; that must
# not disable iTerm2 or Kitty graphics.
assert artwork.detect_protocol({"ZELLIJ": "0", "TERM_PROGRAM": "iTerm.app"}) == "iterm2"
assert artwork.detect_protocol({"ZELLIJ": "", "TERM_PROGRAM": "iTerm.app"}) == "iterm2"
assert artwork.detect_protocol({"TMUX": "0", "KITTY_WINDOW_ID": "1"}) == "tgp"
# ...while a real multiplexer still downgrades to the mosaic.
assert artwork.detect_protocol({"TMUX": "/tmp/tmux-501/default,1,0", "TERM_PROGRAM": "iTerm.app"}) is None
assert artwork.detect_protocol({"STY": "1234.pts-0.host", "TERM_PROGRAM": "iTerm.app"}) is None