from __future__ import annotations import re import types import pytest from rich.cells import cell_len from beefy_player.providers import PROVIDERS from beefy_player.providers.somafm import SomaFMProvider from beefy_player.providers.wordmark import render from beefy_player.widgets.header import HeaderBar TIME = re.compile(r"\d\d:\d\d:\d\d$") DATE = re.compile(r"[A-Z][a-z]{2} \d\d [A-Z][a-z]{2}$") VOLUME = re.compile(r"vol \d+%$") def render_header( width: int, height: int = 3, provider=None ) -> list[str]: """Render the header without a running app, as plain text per line.""" fake = type("Fake", (), {})() fake.app = types.SimpleNamespace( playing_state="playing", volume=70, current=None, notice=None, provider=provider or SomaFMProvider(), ) fake.size = types.SimpleNamespace(width=width, height=height) return [line.plain.rstrip() for line in HeaderBar.render(fake).renderables] @pytest.mark.parametrize("width", [54, 60, 100, 143, 200]) def test_right_block_is_flush_right(width: int) -> None: """Clock, date and volume all end on the same column, at the edge.""" clock, date, volume = render_header(width) assert [cell_len(line) for line in (clock, date, volume)] == [width] * 3 assert TIME.search(clock) assert DATE.search(date) assert VOLUME.search(volume) def test_ragged_middle_does_not_shift_the_right_block() -> None: """"TILDERADIO" is shorter than "STREAMED BY / FOR USERS OF THE"; the right block must not follow the length of the middle lines.""" clock, date, volume = render_header(143) assert cell_len(clock) == cell_len(date) == cell_len(volume) == 143 def test_right_block_hidden_on_narrow_terminals() -> None: """Below the threshold the right block is dropped, not squashed.""" lines = render_header(30) assert all(":" not in line for line in lines) def test_somafm_hero_spells_the_whole_name_and_its_taglines() -> None: lines = render_header(143) assert lines[0].startswith(render("SOMAFM")[0]) assert "SOMA FM RADIO" in lines[0] assert "LISTENER SUPPORTED" in lines[1] assert "SAN FRANCISCO" in lines[2] def hero_styles(provider) -> list[str]: return [ str(span.style) for line in HeaderBar.render(_fake(provider)).renderables for span in line.spans ] def test_the_i_and_t_have_thin_stems() -> None: """Two-pixel stems make an I look like an H and a T look like a block.""" assert render("I") == ("▀█▀", " █", "▄█▄") assert render("T") == ("▀█▀", " █", " █") def test_the_font_still_spells_soma_the_way_the_original_logo_did() -> None: """The wordmark font is the SomaFM logo, so the old art must be reproduced.""" assert render("SOMA") == ( "▄▀▀▀ ▄▀▀▄ █▄ ▄█ ▄▀▀▄", "▀▀▀▄ █ █ █ █ █ █▀▀█", "▄▄▄▀ ▀▄▄▀ █ █ █ █", ) def test_every_catalogue_has_a_three_row_wordmark() -> None: for slug, provider in PROVIDERS.items(): assert len(provider.hero) == 3, slug assert provider.hero_lines, slug def test_tilderadio_spells_its_own_name_in_the_default_colour() -> None: provider = PROVIDERS["tilderadio"] lines = render_header(143, height=len(provider.hero), provider=provider) assert len(lines) == 3 assert "TILDERADIO" in lines[0] # the wordmark assert "STREAMED BY / FOR USERS OF THE" in lines[1] assert "TILDEVERSE" in lines[2] # no colour of its own: the art inherits the terminal foreground assert all("[" not in row for row in provider.hero) assert not {"bright_cyan", "cyan"} & set(hero_styles(provider)) assert all("SOMA" not in line for line in lines) def test_bassdrive_spells_its_name_in_its_own_two_blues() -> None: provider = PROVIDERS["bassdrive"] lines = render_header(143, height=len(provider.hero), provider=provider) assert "BASSDRIVE" in lines[0] assert "WORLDWIDE DRUM & BASS" in lines[1] assert "LIVE SHOWS 24/7" in lines[2] styles = hero_styles(provider) assert "bright_cyan" in styles and "cyan" in styles def _fake(provider): fake = type("Fake", (), {})() fake.app = types.SimpleNamespace( playing_state="playing", volume=70, current=None, notice=None, provider=provider, ) fake.size = types.SimpleNamespace(width=143, height=len(provider.hero) or 3) return fake