Files
beefy-player/tests/test_spectrum_panel.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

104 lines
3.7 KiB
Python

"""Rendering tests for the waterfall, radial and braille visualisers."""
from __future__ import annotations
import math
from beefy_player import config
from beefy_player.spectrum import Frame, SpectrumEngine
from beefy_player.widgets.spectrum import (
VISUALIZER_LABELS,
WATERFALL_HISTORY,
SpectrumPanel,
)
def make_frame(level: float = 0.7) -> Frame:
bands = tuple(0.12 + 0.8 * max(0.0, math.sin(index / 4.0)) for index in range(72))
peaks = tuple(min(1.0, value + 0.05) for value in bands)
wave = tuple(level * math.sin(point / 9.0) for point in range(192))
return Frame(bands=bands, peaks=peaks, wave=wave, level=level, status="monitor")
def make_panel() -> SpectrumPanel:
return SpectrumPanel(SpectrumEngine())
def test_every_visualizer_has_a_border_label() -> None:
assert set(VISUALIZER_LABELS) == set(config.VISUALIZERS)
def test_waterfall_records_one_column_per_tick() -> None:
panel = make_panel()
frame = make_frame()
panel._record(frame, tick=1)
panel._record(frame, tick=2)
panel._record(frame, tick=2) # a second render in the same tick
assert len(panel._history) == 2
for tick in range(3, WATERFALL_HISTORY + 20):
panel._record(frame, tick)
assert len(panel._history) == WATERFALL_HISTORY
def test_waterfall_scrolls_spectrogram_with_bass_at_the_bottom() -> None:
panel = make_panel()
# Quieter highs, louder bass, so the bottom rows must be fuller.
rising = Frame(
bands=tuple((71 - index) / 71 for index in range(72)),
peaks=tuple((71 - index) / 71 for index in range(72)),
wave=(0.0,) * 192,
level=0.5,
status="monitor",
)
for tick in range(30):
panel._record(rising, tick)
lines = panel._waterfall(40, 12, dim=False)
assert len(lines) == 12
assert {len(line.plain) for line in lines} == {40}
chars = "".join(line.plain for line in lines)
assert any(char in chars for char in "░▒▓█")
# History that does not fill the panel is padded blank on the left.
assert lines[0].plain[:9].strip() == ""
filled = [sum(1 for char in line.plain if char != " ") for line in lines]
assert filled[-1] > filled[0]
def test_radial_spectrum_draws_a_ring_inside_the_panel() -> None:
panel = make_panel()
lines = panel._radial(make_frame(), 61, 21, phase=0, dim=False)
assert len(lines) == 21
assert {len(line.plain) for line in lines} == {61}
text = "".join(line.plain for line in lines)
assert text.count("█") > 10
assert text.count("·") > 4
# The corners are outside the circle.
assert lines[2].plain[:6].strip() == ""
assert lines[2].plain[-6:].strip() == ""
def test_radial_falls_back_to_bars_when_too_small() -> None:
panel = make_panel()
lines = panel._radial(make_frame(), 12, 4, phase=0, dim=False)
assert len(lines) == 4
assert {len(line.plain) for line in lines} == {12}
assert any(char in "".join(line.plain for line in lines) for char in "█▄▅")
def test_braille_oscilloscope_uses_subcell_dots() -> None:
panel = make_panel()
lines = panel._braille(make_frame(0.9), 40, 8, phase=0, dim=False)
assert len(lines) == 8
assert {len(line.plain) for line in lines} == {40}
chars = "".join(line.plain for line in lines)
assert all(char == " " or 0x2800 <= ord(char) <= 0x28FF for char in chars)
dots = [char for char in chars if 0x2800 <= ord(char) <= 0x28FF]
assert len(dots) > 20
def test_braille_keeps_a_baseline_when_silent() -> None:
panel = make_panel()
silent = Frame((0.0,) * 72, (0.0,) * 72, (0.0,) * 192, 0.0, "idle")
lines = panel._braille(silent, 20, 6, phase=0, dim=True)
chars = "".join(line.plain for line in lines)
assert any(0x2800 <= ord(char) <= 0x28FF for char in chars)