test: cover app and playback controller mixin behavior
This commit is contained in:
148
tests/app/test_app_progress_mixin_behavior.py
Normal file
148
tests/app/test_app_progress_mixin_behavior.py
Normal file
@@ -0,0 +1,148 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import cast
|
||||
|
||||
from auditui.app.progress import AppProgressMixin
|
||||
from textual.events import Key
|
||||
from textual.widgets import DataTable
|
||||
|
||||
|
||||
@dataclass(slots=True)
|
||||
class FakeKeyEvent:
|
||||
"""Minimal key event carrying key value and prevent_default state."""
|
||||
|
||||
key: str
|
||||
prevented: bool = False
|
||||
|
||||
def prevent_default(self) -> None:
|
||||
"""Mark event as prevented."""
|
||||
self.prevented = True
|
||||
|
||||
|
||||
@dataclass(slots=True)
|
||||
class FakeStatic:
|
||||
"""Minimal static widget with text and visibility fields."""
|
||||
|
||||
display: bool = False
|
||||
text: str = ""
|
||||
|
||||
def update(self, value: str) -> None:
|
||||
"""Store rendered text value."""
|
||||
self.text = value
|
||||
|
||||
|
||||
@dataclass(slots=True)
|
||||
class FakeProgressBar:
|
||||
"""Minimal progress bar widget storing latest progress value."""
|
||||
|
||||
progress: float = 0.0
|
||||
|
||||
def update(self, progress: float) -> None:
|
||||
"""Store progress value for assertions."""
|
||||
self.progress = progress
|
||||
|
||||
|
||||
@dataclass(slots=True)
|
||||
class FakeContainer:
|
||||
"""Minimal container exposing display property."""
|
||||
|
||||
display: bool = False
|
||||
|
||||
|
||||
class DummyPlayback:
|
||||
"""Playback shim exposing only members used by AppProgressMixin."""
|
||||
|
||||
def __init__(self) -> None:
|
||||
"""Initialize playback state and update counters."""
|
||||
self.is_playing = False
|
||||
self._status: str | None = None
|
||||
self._progress: tuple[str, float, float] | None = None
|
||||
self.saved_calls = 0
|
||||
|
||||
def check_status(self):
|
||||
"""Return configurable status check message."""
|
||||
return self._status
|
||||
|
||||
def get_current_progress(self):
|
||||
"""Return configurable progress tuple."""
|
||||
return self._progress
|
||||
|
||||
def update_position_if_needed(self) -> None:
|
||||
"""Record periodic save invocations."""
|
||||
self.saved_calls += 1
|
||||
|
||||
|
||||
class DummyProgressApp(AppProgressMixin):
|
||||
"""Mixin host that records action dispatch and widget updates."""
|
||||
|
||||
def __init__(self) -> None:
|
||||
"""Initialize fake widgets and playback state."""
|
||||
self.playback = DummyPlayback()
|
||||
self.focused = object()
|
||||
self.actions: list[str] = []
|
||||
self.messages: list[str] = []
|
||||
self.progress_info = FakeStatic()
|
||||
self.progress_bar = FakeProgressBar()
|
||||
self.progress_container = FakeContainer()
|
||||
|
||||
def action_seek_backward(self) -> None:
|
||||
"""Record backward seek action dispatch."""
|
||||
self.actions.append("seek_backward")
|
||||
|
||||
def action_toggle_playback(self) -> None:
|
||||
"""Record toggle playback action dispatch."""
|
||||
self.actions.append("toggle")
|
||||
|
||||
def update_status(self, message: str) -> None:
|
||||
"""Capture status messages for assertions."""
|
||||
self.messages.append(message)
|
||||
|
||||
def query_one(self, selector: str, _type: object):
|
||||
"""Return fake widgets by selector used by progress mixin."""
|
||||
return {
|
||||
"#progress_info": self.progress_info,
|
||||
"#progress_bar": self.progress_bar,
|
||||
"#progress_bar_container": self.progress_container,
|
||||
}[selector]
|
||||
|
||||
|
||||
def test_on_key_dispatches_seek_when_playing() -> None:
|
||||
"""Ensure left key is intercepted and dispatched to seek action."""
|
||||
app = DummyProgressApp()
|
||||
app.playback.is_playing = True
|
||||
event = FakeKeyEvent("left")
|
||||
app.on_key(cast(Key, event))
|
||||
assert event.prevented is True
|
||||
assert app.actions == ["seek_backward"]
|
||||
|
||||
|
||||
def test_on_key_dispatches_space_when_table_focused() -> None:
|
||||
"""Ensure space is intercepted and dispatched when table is focused."""
|
||||
app = DummyProgressApp()
|
||||
app.focused = DataTable()
|
||||
event = FakeKeyEvent("space")
|
||||
app.on_key(cast(Key, event))
|
||||
assert event.prevented is True
|
||||
assert app.actions == ["toggle"]
|
||||
|
||||
|
||||
def test_check_playback_status_hides_progress_after_message() -> None:
|
||||
"""Ensure playback status message triggers hide-progress behavior."""
|
||||
app = DummyProgressApp()
|
||||
app.playback._status = "Finished"
|
||||
app._check_playback_status()
|
||||
assert app.messages[-1] == "Finished"
|
||||
assert app.progress_info.display is False
|
||||
assert app.progress_container.display is False
|
||||
|
||||
|
||||
def test_update_progress_renders_visible_progress_row() -> None:
|
||||
"""Ensure valid progress data updates widgets and makes them visible."""
|
||||
app = DummyProgressApp()
|
||||
app.playback.is_playing = True
|
||||
app.playback._progress = ("Chapter", 30.0, 60.0)
|
||||
app._update_progress()
|
||||
assert app.progress_bar.progress == 50.0
|
||||
assert app.progress_info.display is True
|
||||
assert app.progress_container.display is True
|
||||
Reference in New Issue
Block a user