youtube-cli/tests/unit/test_widgets.py
Jarian Cottingham 06877fb2fc feat: add TUI (Textual User Interface) with download queue system
- New TUI using Textual framework with responsive interface
- Search, results, download, and queue screens for YouTube video management
- Download queue system with sequential background downloads
- Status bar with queue count and download progress indicators
- Comprehensive test suite (97 tests) with 100% pass rate
- Modern Python packaging with pyproject.toml and uv support
- Added requirements-tui.txt for TUI-specific dependencies
- Updated setup.py and install.sh for TUI integration
- Enhanced README.md with TUI usage documentation
2026-02-25 15:59:15 -06:00

232 lines
7.4 KiB
Python

"""
Unit tests for TUI widgets
"""
from datetime import datetime
from unittest.mock import MagicMock, patch
import pytest
class TestStatusBar:
"""Tests for StatusBar widget"""
@pytest.fixture
def mock_app(self):
"""Mock Textual app"""
from unittest.mock import MagicMock
from textual.app import App
# Create a minimal mock app that works with Textual
app = MagicMock(spec=App)
app.theme = "default"
app.VERSION = "1.0.0"
return app
def test_statusbar_initialization(self, mock_app):
"""Test StatusBar initialization"""
from youtube_tui.widgets.status_bar import StatusBar
# Mock subprocess.run
with patch("subprocess.run") as mock_run:
mock_result = MagicMock()
mock_result.returncode = 0
mock_result.stdout = "2024.01.01"
mock_run.return_value = mock_result
status_bar = StatusBar(mock_app)
assert status_bar.current_screen == "Search"
assert status_bar.status_message == "Ready"
assert status_bar.downloading is False
assert status_bar.yt_dlp_version == "2024.01.01"
def test_statusbar_update_version_success(self, mock_app):
"""Test version update with successful yt-dlp call"""
from youtube_tui.widgets.status_bar import StatusBar
with patch("subprocess.run") as mock_run:
mock_result = MagicMock()
mock_result.returncode = 0
mock_result.stdout = "2024.01.15"
mock_run.return_value = mock_result
status_bar = StatusBar(mock_app)
status_bar.update_version()
assert status_bar.yt_dlp_version == "2024.01.15"
def test_statusbar_update_version_not_installed(self, mock_app):
"""Test version update when yt-dlp not installed"""
from youtube_tui.widgets.status_bar import StatusBar
with patch("subprocess.run") as mock_run:
mock_result = MagicMock()
mock_result.returncode = 1
mock_run.return_value = mock_result
status_bar = StatusBar(mock_app)
status_bar.update_version()
assert status_bar.yt_dlp_version == "not installed"
def test_statusbar_update_version_error(self, mock_app):
"""Test version update with error"""
from youtube_tui.widgets.status_bar import StatusBar
with patch("subprocess.run") as mock_run:
mock_run.side_effect = Exception("Command failed")
status_bar = StatusBar(mock_app)
status_bar.update_version()
assert status_bar.yt_dlp_version == "unknown"
def test_statusbar_set_screen(self, mock_app):
"""Test setting screen name"""
from youtube_tui.widgets.status_bar import StatusBar
with patch("subprocess.run"):
status_bar = StatusBar(mock_app)
status_bar.set_screen("Results")
assert status_bar.current_screen == "Results"
def test_statusbar_set_downloading(self, mock_app):
"""Test setting downloading state"""
from youtube_tui.widgets.status_bar import StatusBar
with patch("subprocess.run"):
status_bar = StatusBar(mock_app)
status_bar.set_downloading(True)
assert status_bar.downloading is True
status_bar.set_downloading(False)
assert status_bar.downloading is False
def test_statusbar_set_status(self, mock_app):
"""Test setting status message"""
from youtube_tui.widgets.status_bar import StatusBar
with patch("subprocess.run"):
status_bar = StatusBar(mock_app)
status_bar.set_status("Downloading video...")
assert status_bar.status_message == "Downloading video..."
def test_statusbar_render(self, mock_app):
"""Test status bar rendering"""
from datetime import datetime
from youtube_tui.widgets.status_bar import StatusBar
with patch("subprocess.run") as mock_run:
mock_result = MagicMock()
mock_result.returncode = 0
mock_result.stdout = "2024.01.01"
mock_run.return_value = mock_result
status_bar = StatusBar(mock_app)
status_bar.set_status("Ready")
# Mock datetime for consistent testing
with patch(
"youtube_tui.widgets.status_bar.datetime"
) as mock_datetime:
mock_datetime.now.return_value = datetime(
2024, 1, 15, 12, 30, 0
)
render_result = status_bar.render()
render_str = str(render_result)
assert "Search" in render_str
assert "2024.01.01" in render_str
assert "Ready" in render_str
assert "12:30:00" in render_str
def test_statusbar_render_downloading(self, mock_app):
"""Test status bar rendering with downloading indicator"""
from youtube_tui.widgets.status_bar import StatusBar
with patch("subprocess.run") as mock_run:
mock_result = MagicMock()
mock_result.returncode = 0
mock_result.stdout = "2024.01.01"
mock_run.return_value = mock_result
status_bar = StatusBar(mock_app)
status_bar.set_downloading(True)
with patch(
"youtube_tui.widgets.status_bar.datetime"
) as mock_datetime:
mock_datetime.now.return_value = datetime(
2024, 1, 15, 12, 30, 0
)
render_result = status_bar.render()
render_str = str(render_result)
assert "" in render_str # Download indicator
class TestCommandPalette:
"""Tests for CommandPalette widget"""
def test_command_palette_creation(self):
"""Test CommandPalette initialization"""
from youtube_tui.widgets.command_palette import CommandPalette
palette = CommandPalette()
assert palette is not None
class TestCustomWidgets:
"""Tests for custom widget implementations"""
def test_static_widget_creation(self):
"""Test basic Static widget"""
from textual.widgets import Static
widget = Static("Test content")
assert widget._Static__content == "Test content"
def test_static_widget_with_rich_markup(self):
"""Test Static widget with Rich markup"""
from textual.widgets import Static
widget = Static("[bold]Test[/bold] [red]content[/red]")
# The content should contain the markup
assert "[bold]" in str(widget._Static__content)
def test_button_widget_creation(self):
"""Test Button widget"""
from textual.widgets import Button
button = Button("Click me")
assert button.label == "Click me"
def test_input_widget_creation(self):
"""Test Input widget"""
from textual.widgets import Input
input_widget = Input(placeholder="Enter text...")
assert input_widget.placeholder == "Enter text..."
def test_data_table_creation(self):
"""Test DataTable widget"""
from textual.widgets import DataTable
table = DataTable()
assert table is not None
def test_progress_bar_creation(self):
"""Test ProgressBar widget"""
from textual.widgets import ProgressBar
progress = ProgressBar(total=100)
progress.progress = 50
assert progress.total == 100
assert progress.progress == 50