test: cover app and playback controller mixin behavior
This commit is contained in:
124
tests/app/test_app_actions_selection_and_controls.py
Normal file
124
tests/app/test_app_actions_selection_and_controls.py
Normal file
@@ -0,0 +1,124 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
|
||||
from auditui.app.actions import AppActionsMixin
|
||||
|
||||
|
||||
@dataclass(slots=True)
|
||||
class FakeTable:
|
||||
"""Minimal table shim exposing cursor and row count."""
|
||||
|
||||
row_count: int
|
||||
cursor_row: int = 0
|
||||
|
||||
|
||||
class FakePlayback:
|
||||
"""Playback stub with togglable boolean return values."""
|
||||
|
||||
def __init__(self, result: bool) -> None:
|
||||
"""Store deterministic toggle result for tests."""
|
||||
self._result = result
|
||||
self.calls: list[str] = []
|
||||
|
||||
def toggle_playback(self) -> bool:
|
||||
"""Return configured result and record call."""
|
||||
self.calls.append("toggle")
|
||||
return self._result
|
||||
|
||||
def seek_forward(self, _seconds: float) -> bool:
|
||||
"""Return configured result and record call."""
|
||||
self.calls.append("seek_forward")
|
||||
return self._result
|
||||
|
||||
|
||||
class DummyActionsApp(AppActionsMixin):
|
||||
"""Mixin host with just enough state for action method tests."""
|
||||
|
||||
def __init__(self) -> None:
|
||||
"""Initialize fake app state used by action helpers."""
|
||||
self.messages: list[str] = []
|
||||
self.current_items: list[dict] = []
|
||||
self.download_manager = object()
|
||||
self.library_client = type(
|
||||
"Library", (), {"extract_asin": lambda self, item: item.get("asin")}
|
||||
)()
|
||||
self.playback = FakePlayback(True)
|
||||
self.filter_text = "hello"
|
||||
self._refreshed = 0
|
||||
self._table = FakeTable(row_count=0, cursor_row=0)
|
||||
|
||||
def update_status(self, message: str) -> None:
|
||||
"""Collect status messages for assertions."""
|
||||
self.messages.append(message)
|
||||
|
||||
def query_one(self, selector: str, _type: object) -> FakeTable:
|
||||
"""Return the fake table used in selection tests."""
|
||||
assert selector == "#library_table"
|
||||
return self._table
|
||||
|
||||
def _refresh_filtered_view(self) -> None:
|
||||
"""Record refresh invocations for filter tests."""
|
||||
self._refreshed += 1
|
||||
|
||||
def _start_playback_async(self, asin: str) -> None:
|
||||
"""Capture async playback launch argument."""
|
||||
self.messages.append(f"start:{asin}")
|
||||
|
||||
|
||||
def test_get_selected_asin_requires_non_empty_table() -> None:
|
||||
"""Ensure selection fails gracefully when table has no rows."""
|
||||
app = DummyActionsApp()
|
||||
app._table = FakeTable(row_count=0)
|
||||
assert app._get_selected_asin() is None
|
||||
assert app.messages[-1] == "No books available"
|
||||
|
||||
|
||||
def test_get_selected_asin_returns_current_row_asin() -> None:
|
||||
"""Ensure selected row index maps to current_items ASIN."""
|
||||
app = DummyActionsApp()
|
||||
app._table = FakeTable(row_count=2, cursor_row=1)
|
||||
app.current_items = [{"asin": "A1"}, {"asin": "A2"}]
|
||||
assert app._get_selected_asin() == "A2"
|
||||
|
||||
|
||||
def test_action_play_selected_starts_async_playback() -> None:
|
||||
"""Ensure play action calls async starter with selected ASIN."""
|
||||
app = DummyActionsApp()
|
||||
app._table = FakeTable(row_count=1, cursor_row=0)
|
||||
app.current_items = [{"asin": "ASIN"}]
|
||||
app.action_play_selected()
|
||||
assert app.messages[-1] == "start:ASIN"
|
||||
|
||||
|
||||
def test_action_toggle_playback_shows_hint_when_no_playback() -> None:
|
||||
"""Ensure toggle action displays no-playback hint on false return."""
|
||||
app = DummyActionsApp()
|
||||
app.playback = FakePlayback(False)
|
||||
app.action_toggle_playback()
|
||||
assert app.messages[-1] == "No playback active. Press Enter to play a book."
|
||||
|
||||
|
||||
def test_action_seek_forward_shows_hint_when_seek_fails() -> None:
|
||||
"""Ensure failed seek action reuses no-playback helper status."""
|
||||
app = DummyActionsApp()
|
||||
app.playback = FakePlayback(False)
|
||||
app.action_seek_forward()
|
||||
assert app.messages[-1] == "No playback active. Press Enter to play a book."
|
||||
|
||||
|
||||
def test_action_clear_filter_resets_filter_and_refreshes() -> None:
|
||||
"""Ensure clearing filter resets text and refreshes filtered view."""
|
||||
app = DummyActionsApp()
|
||||
app.action_clear_filter()
|
||||
assert app.filter_text == ""
|
||||
assert app._refreshed == 1
|
||||
assert app.messages[-1] == "Filter cleared"
|
||||
|
||||
|
||||
def test_apply_filter_coerces_none_to_empty_string() -> None:
|
||||
"""Ensure apply_filter normalizes None and refreshes list view."""
|
||||
app = DummyActionsApp()
|
||||
app._apply_filter(None)
|
||||
assert app.filter_text == ""
|
||||
assert app._refreshed == 1
|
||||
Reference in New Issue
Block a user