Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions fluxtuner/theme_runtime.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from typing import Any

from textual.app import App
from textual.widget import Widget
from textual.widgets import Button, Footer, Header

from fluxtuner.logging_config import get_logger
Expand Down Expand Up @@ -168,7 +169,7 @@ def apply_theme_runtime(app: App[Any], theme_name: str) -> Path:
if "Screen" in rules:
_apply_declarations(app.screen, rules["Screen"])

selector_map = {
selector_map: dict[str, str | type[Widget]] = {
"Header": Header,
"Footer": Footer,
"Button": Button,
Expand Down Expand Up @@ -198,7 +199,7 @@ def apply_theme_runtime(app: App[Any], theme_name: str) -> Path:
if not declarations:
continue
try:
widgets = app.query(target) if isinstance(target, str) else app.query(target)
widgets = app.query(target)
for widget in widgets:
_apply_declarations(widget, declarations)
except Exception: # noqa: BLE001
Expand Down
19 changes: 8 additions & 11 deletions fluxtuner/tui.py
Original file line number Diff line number Diff line change
Expand Up @@ -1783,13 +1783,10 @@ def ensure_now_playing_layout(self) -> None:
now_playing.styles.padding = (1, 2)
now_playing.styles.content_align = ("left", "top")
now_playing.styles.text_align = "left"
now_playing.styles.overflow = "hidden"
now_playing.styles.overflow_x = "hidden"
now_playing.styles.overflow_y = "hidden"
now_playing.styles.text_overflow = "ellipsis"
# Textual versions differ here; ignore unsupported style names.
try:
now_playing.styles.white_space = "normal"
except Exception: # noqa: BLE001
logger.debug("Textual style property not supported: white_space", exc_info=True)
now_playing.styles.text_wrap = "wrap"
except Exception: # noqa: BLE001
logger.debug("Could not apply Now Playing layout adjustments", exc_info=True)

Expand All @@ -1799,7 +1796,7 @@ def _start_usage_tracking(self, station: dict[str, Any]) -> None:

bitrate = int(station.get("bitrate") or 0)
if bitrate <= 0:
if self.is_mounted:
if self.is_running:
self.query_one("#data-usage", Static).update(
"[b]Data usage[/b]\nUnavailable · missing bitrate"
)
Expand All @@ -1813,7 +1810,7 @@ def _start_usage_tracking(self, station: dict[str, Any]) -> None:
start(station)

def _update_usage_display(self) -> None:
if not self.is_mounted:
if not self.is_running:
return
with suppress(Exception):
usage_line = format_usage_line(self.usage_tracker.snapshot())
Expand All @@ -1834,7 +1831,7 @@ def _player_state_text(self) -> str:
return "playing" if self.playing_station else "stopped"

def _update_player_state_display(self) -> None:
if not self.is_mounted:
if not self.is_running:
return

state = self._player_state_snapshot()
Expand All @@ -1860,7 +1857,7 @@ def _clear_metadata(self) -> None:
self.current_artist = "—"
self.current_track = "—"
self.metadata_lifecycle.reset_projection()
if self.is_mounted:
if self.is_running:
self.query_one("#metadata", Static).update("[b]Metadata[/b]\nArtist: —\nTrack: —")

def _cancel_metadata_request(self) -> None:
Expand Down Expand Up @@ -1900,7 +1897,7 @@ async def _fetch_metadata(self, stream_url: str, request_id: int) -> None:

self.current_artist = projection.artist
self.current_track = projection.track
if self.is_mounted:
if self.is_running:
self.query_one("#metadata", Static).update(
f"[b]Metadata[/b]\nArtist: {self.current_artist}\nTrack: {self.current_track}"
)
Expand Down
2 changes: 1 addition & 1 deletion fluxtuner/web/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ async def resolve_and_renew_session(request: Request, call_next: Any) -> Respons
)

response = await call_next(request)
if renewed_cookie_max_age is not None:
if token and renewed_cookie_max_age is not None:
set_session_cookie(response, token, max_age=renewed_cookie_max_age)
return response

Expand Down
48 changes: 47 additions & 1 deletion tests/test_tui_metadata_lifecycle.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ def _metadata_harness(*, mounted: bool = False) -> SimpleNamespace:
current_artist="Old artist",
current_track="Old track",
metadata_lifecycle=MetadataLifecycle(),
is_mounted=mounted,
is_running=mounted,
query_one=Mock(return_value=widget),
station_url=Mock(return_value="https://radio.example/stream"),
)
Expand Down Expand Up @@ -441,3 +441,49 @@ def test_metadata_lifecycle_rejects_previous_request_for_same_url() -> None:

assert projection is None
assert lifecycle.last_raw is None


# N9_TUI_RUNNING_CONTRACTS


def test_clear_metadata_without_running_app_does_not_touch_widget() -> None:
harness = _metadata_harness(mounted=False)

tui.FluxTunerTUI._clear_metadata(harness)

assert harness.current_artist == "—"
assert harness.current_track == "—"
assert harness.metadata_lifecycle.last_raw is None
harness.query_one.assert_not_called()


def test_usage_display_without_running_app_does_not_query_widget() -> None:
harness = _metadata_harness(mounted=False)

tui.FluxTunerTUI._update_usage_display(harness)

harness.query_one.assert_not_called()


def test_player_display_without_running_app_does_not_query_widget() -> None:
harness = _metadata_harness(mounted=False)

tui.FluxTunerTUI._update_player_state_display(harness)

harness.query_one.assert_not_called()


def test_missing_bitrate_only_updates_widget_while_running() -> None:
for running in (False, True):
harness = _metadata_harness(mounted=running)
harness.usage_tracker = Mock()

tui.FluxTunerTUI._start_usage_tracking(harness, {"name": "Unknown bitrate", "bitrate": 0})

harness.usage_tracker.stop.assert_called_once_with()
if running:
harness.metadata_widget.update.assert_called_once_with(
"[b]Data usage[/b]\nUnavailable · missing bitrate"
)
else:
harness.query_one.assert_not_called()
Loading