""" Tests for ProgressWidget UI component. """ import pytest # Skip tests if PyQt6 is not installed pytest.importorskip("PyQt6") from datetime import datetime, timedelta from unittest.mock import Mock from PyQt6.QtWidgets import QApplication from PyQt6.QtCore import Qt, QTimer from PyQt6.QtTest import QTest from src.ui.progress_widget import ProgressWidget, ChapterProgressItem from src.scheduler.progress import ProgressNotifier from src.scheduler.models import ChapterTask, PipelineProgress, TaskStatus, SchedulerState @pytest.fixture def app(qtbot): """Create QApplication fixture.""" test_app = QApplication.instance() if test_app is None: test_app = QApplication([]) yield test_app @pytest.fixture def progress_widget(app, qtbot): """Create ProgressWidget fixture.""" widget = ProgressWidget() qtbot.addWidget(widget) yield widget widget.close() @pytest.fixture def sample_chapter_tasks(): """Create sample chapter tasks for testing.""" return [ ChapterTask( chapter_id=f"ch_{i}", chapter_index=i, title=f"Chapter {i + 1}", original_content=f"Content for chapter {i + 1}\n", ) for i in range(5) ] class TestProgressWidget: """Test ProgressWidget functionality.""" def test_initialization(self, progress_widget): """Test progress widget initializes correctly.""" assert progress_widget._progress is None assert progress_widget._start_time is None assert len(progress_widget._chapter_items) == 0 assert not progress_widget._eta_timer.isActive() def test_initial_ui_state(self, progress_widget): """Test initial UI state.""" assert "No active translation" in progress_widget._stage_label.text() assert progress_widget._progress_bar.value() == 0 assert progress_widget._completed_label.text() == "Completed: 0" assert progress_widget._failed_label.text() == "Failed: 0" assert progress_widget._remaining_label.text() == "Remaining: 0" def test_connect_to_notifier(self, progress_widget): """Test connecting to ProgressNotifier.""" notifier = ProgressNotifier() progress_widget.connect_to_notifier(notifier) assert notifier.observer_count == 1 def test_disconnect_from_notifier(self, progress_widget): """Test disconnecting from ProgressNotifier.""" notifier = ProgressNotifier() progress_widget.connect_to_notifier(notifier) assert notifier.observer_count == 1 progress_widget.disconnect_from_notifier(notifier) assert notifier.observer_count == 0 def test_on_pipeline_start(self, progress_widget, sample_chapter_tasks): """Test pipeline start event.""" notifier = ProgressNotifier() progress_widget.connect_to_notifier(notifier) notifier.notify_pipeline_start(5) assert progress_widget._progress is not None assert progress_widget._progress.total_chapters == 5 assert progress_widget._progress.state == SchedulerState.RUNNING assert progress_widget._start_time is not None assert progress_widget._eta_timer.isActive() def test_on_pipeline_complete(self, progress_widget): """Test pipeline complete event.""" notifier = ProgressNotifier() progress_widget.connect_to_notifier(notifier) # Start the pipeline notifier.notify_pipeline_start(5) # Complete the pipeline progress = PipelineProgress( total_chapters=5, completed_chapters=5, state=SchedulerState.COMPLETED ) notifier.notify_pipeline_complete(progress) assert not progress_widget._eta_timer.isActive() assert progress_widget._progress_bar.value() == 100 def test_on_pipeline_paused(self, progress_widget): """Test pipeline paused event.""" notifier = ProgressNotifier() progress_widget.connect_to_notifier(notifier) progress = PipelineProgress( total_chapters=5, completed_chapters=2, current_chapter=2, state=SchedulerState.PAUSED ) notifier.notify_pipeline_paused(progress) assert "paused" in progress_widget._stage_label.text().lower() def test_on_pipeline_resumed(self, progress_widget): """Test pipeline resumed event.""" notifier = ProgressNotifier() progress_widget.connect_to_notifier(notifier) progress = PipelineProgress( total_chapters=5, completed_chapters=2, current_chapter=2, state=SchedulerState.RUNNING ) notifier.notify_pipeline_resumed(progress) assert progress_widget._eta_timer.isActive() def test_on_chapter_start(self, progress_widget, sample_chapter_tasks): """Test chapter start event.""" notifier = ProgressNotifier() progress_widget.connect_to_notifier(notifier) notifier.notify_pipeline_start(5) notifier.notify_chapter_start(sample_chapter_tasks[0]) assert len(progress_widget._chapter_items) == 1 def test_on_chapter_complete(self, progress_widget, sample_chapter_tasks): """Test chapter complete event.""" notifier = ProgressNotifier() progress_widget.connect_to_notifier(notifier) notifier.notify_pipeline_start(5) # Mark task as completed task = sample_chapter_tasks[0] task.status = TaskStatus.COMPLETED notifier.notify_chapter_complete(task) assert progress_widget._progress.completed_chapters == 1 def test_on_chapter_failed(self, progress_widget, sample_chapter_tasks): """Test chapter failed event.""" notifier = ProgressNotifier() progress_widget.connect_to_notifier(notifier) notifier.notify_pipeline_start(5) # Mark task as failed task = sample_chapter_tasks[0] task.status = TaskStatus.FAILED task.error_message = "Test error" notifier.notify_chapter_failed(task, "Test error") assert progress_widget._progress.failed_chapters == 1 def test_multiple_chapters(self, progress_widget, sample_chapter_tasks): """Test handling multiple chapters.""" notifier = ProgressNotifier() progress_widget.connect_to_notifier(notifier) notifier.notify_pipeline_start(5) # Start and complete 3 chapters for i in range(3): task = sample_chapter_tasks[i] notifier.notify_chapter_start(task) task.status = TaskStatus.COMPLETED notifier.notify_chapter_complete(task) assert len(progress_widget._chapter_items) == 3 assert progress_widget._progress.completed_chapters == 3 def test_stats_update(self, progress_widget): """Test statistics display updates.""" notifier = ProgressNotifier() progress_widget.connect_to_notifier(notifier) notifier.notify_pipeline_start(10) progress = PipelineProgress( total_chapters=10, completed_chapters=5, failed_chapters=1, state=SchedulerState.RUNNING ) progress_widget._progress = progress progress_widget._update_stats() assert progress_widget._completed_label.text() == "Completed: 5" assert progress_widget._failed_label.text() == "Failed: 1" assert progress_widget._remaining_label.text() == "Remaining: 4" def test_reset(self, progress_widget): """Test resetting the widget.""" notifier = ProgressNotifier() progress_widget.connect_to_notifier(notifier) # Start and add some chapters notifier.notify_pipeline_start(5) task = ChapterTask( chapter_id="ch_0", chapter_index=0, title="Chapter 1", original_content="Content\n" ) notifier.notify_chapter_start(task) assert len(progress_widget._chapter_items) == 1 # Reset progress_widget.reset() assert progress_widget._progress is None assert progress_widget._start_time is None assert len(progress_widget._chapter_items) == 0 assert not progress_widget._eta_timer.isActive() def test_format_time(self, progress_widget): """Test time formatting.""" # Seconds only assert progress_widget._format_time(45) == "00:45" # Minutes and seconds assert progress_widget._format_time(125) == "02:05" # Hours, minutes, seconds assert progress_widget._format_time(3661) == "01:01:01" class TestChapterProgressItem: """Test ChapterProgressItem functionality.""" def test_initialization(self, app): """Test chapter progress item initialization.""" task = ChapterTask( chapter_id="ch_0", chapter_index=0, title="Chapter 1", original_content="Content\n" ) item = ChapterProgressItem(task) assert item._task == task assert item._title_label.text() == "Chapter 1: Chapter 1" def test_status_icons(self, app): """Test status icons are correct.""" task = ChapterTask( chapter_id="ch_0", chapter_index=0, title="Chapter 1", original_content="Content\n" ) item = ChapterProgressItem(task) # Test different statuses for status, expected_icon in ChapterProgressItem.STATUS_ICONS.items(): task.status = status item._update_style() assert item._icon_label.text() == expected_icon def test_update_task(self, app): """Test updating task in item.""" task = ChapterTask( chapter_id="ch_0", chapter_index=0, title="Chapter 1", original_content="Content\n" ) item = ChapterProgressItem(task) # Update task with completion task.status = TaskStatus.COMPLETED task.started_at = datetime.now() task.completed_at = datetime.now() + timedelta(seconds=10) item.update_task(task) assert item._task.status == TaskStatus.COMPLETED assert "Completed" in item._details_label.text() def test_failed_task_display(self, app): """Test failed task displays error message.""" task = ChapterTask( chapter_id="ch_0", chapter_index=0, title="Chapter 1", original_content="Content\n", status=TaskStatus.FAILED, error_message="Translation failed: connection timeout" ) item = ChapterProgressItem(task) assert "Failed" in item._details_label.text() assert "connection timeout" in item._details_label.text() def test_retrying_task_display(self, app): """Test retrying task displays retry count.""" task = ChapterTask( chapter_id="ch_0", chapter_index=0, title="Chapter 1", original_content="Content\n", status=TaskStatus.RETRYING, retry_count=2 ) item = ChapterProgressItem(task) assert "Retrying" in item._details_label.text() assert "2/3" in item._details_label.text() class TestProgressIntegration: """Test ProgressWidget integration with ProgressNotifier.""" def test_full_workflow(self, progress_widget, sample_chapter_tasks): """Test complete translation workflow.""" notifier = ProgressNotifier() progress_widget.connect_to_notifier(notifier) # Start pipeline notifier.notify_pipeline_start(5) assert progress_widget._progress.total_chapters == 5 # Process chapters for i, task in enumerate(sample_chapter_tasks): notifier.notify_chapter_start(task) # Simulate completion task.status = TaskStatus.COMPLETED task.started_at = datetime.now() task.completed_at = datetime.now() + timedelta(seconds=10) notifier.notify_chapter_complete(task) # Complete pipeline progress = progress_widget._progress notifier.notify_pipeline_complete(progress) assert progress_widget._progress_bar.value() == 100 def test_error_recovery(self, progress_widget, sample_chapter_tasks): """Test error and recovery scenario.""" notifier = ProgressNotifier() progress_widget.connect_to_notifier(notifier) # Start notifier.notify_pipeline_start(3) # First chapter succeeds notifier.notify_chapter_start(sample_chapter_tasks[0]) sample_chapter_tasks[0].status = TaskStatus.COMPLETED notifier.notify_chapter_complete(sample_chapter_tasks[0]) # Second chapter fails notifier.notify_chapter_start(sample_chapter_tasks[1]) sample_chapter_tasks[1].status = TaskStatus.FAILED sample_chapter_tasks[1].error_message = "Network error" notifier.notify_chapter_failed(sample_chapter_tasks[1], "Network error") assert progress_widget._progress.completed_chapters == 1 assert progress_widget._progress.failed_chapters == 1