2
0

test_progress_widget.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400
  1. """
  2. Tests for ProgressWidget UI component.
  3. """
  4. import pytest
  5. # Skip tests if PyQt6 is not installed
  6. pytest.importorskip("PyQt6")
  7. from datetime import datetime, timedelta
  8. from unittest.mock import Mock
  9. from PyQt6.QtWidgets import QApplication
  10. from PyQt6.QtCore import Qt, QTimer
  11. from PyQt6.QtTest import QTest
  12. from src.ui.progress_widget import ProgressWidget, ChapterProgressItem
  13. from src.scheduler.progress import ProgressNotifier
  14. from src.scheduler.models import ChapterTask, PipelineProgress, TaskStatus, SchedulerState
  15. @pytest.fixture
  16. def app(qtbot):
  17. """Create QApplication fixture."""
  18. test_app = QApplication.instance()
  19. if test_app is None:
  20. test_app = QApplication([])
  21. yield test_app
  22. @pytest.fixture
  23. def progress_widget(app, qtbot):
  24. """Create ProgressWidget fixture."""
  25. widget = ProgressWidget()
  26. qtbot.addWidget(widget)
  27. yield widget
  28. widget.close()
  29. @pytest.fixture
  30. def sample_chapter_tasks():
  31. """Create sample chapter tasks for testing."""
  32. return [
  33. ChapterTask(
  34. chapter_id=f"ch_{i}",
  35. chapter_index=i,
  36. title=f"Chapter {i + 1}",
  37. original_content=f"Content for chapter {i + 1}\n",
  38. )
  39. for i in range(5)
  40. ]
  41. class TestProgressWidget:
  42. """Test ProgressWidget functionality."""
  43. def test_initialization(self, progress_widget):
  44. """Test progress widget initializes correctly."""
  45. assert progress_widget._progress is None
  46. assert progress_widget._start_time is None
  47. assert len(progress_widget._chapter_items) == 0
  48. assert not progress_widget._eta_timer.isActive()
  49. def test_initial_ui_state(self, progress_widget):
  50. """Test initial UI state."""
  51. assert "No active translation" in progress_widget._stage_label.text()
  52. assert progress_widget._progress_bar.value() == 0
  53. assert progress_widget._completed_label.text() == "Completed: 0"
  54. assert progress_widget._failed_label.text() == "Failed: 0"
  55. assert progress_widget._remaining_label.text() == "Remaining: 0"
  56. def test_connect_to_notifier(self, progress_widget):
  57. """Test connecting to ProgressNotifier."""
  58. notifier = ProgressNotifier()
  59. progress_widget.connect_to_notifier(notifier)
  60. assert notifier.observer_count == 1
  61. def test_disconnect_from_notifier(self, progress_widget):
  62. """Test disconnecting from ProgressNotifier."""
  63. notifier = ProgressNotifier()
  64. progress_widget.connect_to_notifier(notifier)
  65. assert notifier.observer_count == 1
  66. progress_widget.disconnect_from_notifier(notifier)
  67. assert notifier.observer_count == 0
  68. def test_on_pipeline_start(self, progress_widget, sample_chapter_tasks):
  69. """Test pipeline start event."""
  70. notifier = ProgressNotifier()
  71. progress_widget.connect_to_notifier(notifier)
  72. notifier.notify_pipeline_start(5)
  73. assert progress_widget._progress is not None
  74. assert progress_widget._progress.total_chapters == 5
  75. assert progress_widget._progress.state == SchedulerState.RUNNING
  76. assert progress_widget._start_time is not None
  77. assert progress_widget._eta_timer.isActive()
  78. def test_on_pipeline_complete(self, progress_widget):
  79. """Test pipeline complete event."""
  80. notifier = ProgressNotifier()
  81. progress_widget.connect_to_notifier(notifier)
  82. # Start the pipeline
  83. notifier.notify_pipeline_start(5)
  84. # Complete the pipeline
  85. progress = PipelineProgress(
  86. total_chapters=5,
  87. completed_chapters=5,
  88. state=SchedulerState.COMPLETED
  89. )
  90. notifier.notify_pipeline_complete(progress)
  91. assert not progress_widget._eta_timer.isActive()
  92. assert progress_widget._progress_bar.value() == 100
  93. def test_on_pipeline_paused(self, progress_widget):
  94. """Test pipeline paused event."""
  95. notifier = ProgressNotifier()
  96. progress_widget.connect_to_notifier(notifier)
  97. progress = PipelineProgress(
  98. total_chapters=5,
  99. completed_chapters=2,
  100. current_chapter=2,
  101. state=SchedulerState.PAUSED
  102. )
  103. notifier.notify_pipeline_paused(progress)
  104. assert "paused" in progress_widget._stage_label.text().lower()
  105. def test_on_pipeline_resumed(self, progress_widget):
  106. """Test pipeline resumed event."""
  107. notifier = ProgressNotifier()
  108. progress_widget.connect_to_notifier(notifier)
  109. progress = PipelineProgress(
  110. total_chapters=5,
  111. completed_chapters=2,
  112. current_chapter=2,
  113. state=SchedulerState.RUNNING
  114. )
  115. notifier.notify_pipeline_resumed(progress)
  116. assert progress_widget._eta_timer.isActive()
  117. def test_on_chapter_start(self, progress_widget, sample_chapter_tasks):
  118. """Test chapter start event."""
  119. notifier = ProgressNotifier()
  120. progress_widget.connect_to_notifier(notifier)
  121. notifier.notify_pipeline_start(5)
  122. notifier.notify_chapter_start(sample_chapter_tasks[0])
  123. assert len(progress_widget._chapter_items) == 1
  124. def test_on_chapter_complete(self, progress_widget, sample_chapter_tasks):
  125. """Test chapter complete event."""
  126. notifier = ProgressNotifier()
  127. progress_widget.connect_to_notifier(notifier)
  128. notifier.notify_pipeline_start(5)
  129. # Mark task as completed
  130. task = sample_chapter_tasks[0]
  131. task.status = TaskStatus.COMPLETED
  132. notifier.notify_chapter_complete(task)
  133. assert progress_widget._progress.completed_chapters == 1
  134. def test_on_chapter_failed(self, progress_widget, sample_chapter_tasks):
  135. """Test chapter failed event."""
  136. notifier = ProgressNotifier()
  137. progress_widget.connect_to_notifier(notifier)
  138. notifier.notify_pipeline_start(5)
  139. # Mark task as failed
  140. task = sample_chapter_tasks[0]
  141. task.status = TaskStatus.FAILED
  142. task.error_message = "Test error"
  143. notifier.notify_chapter_failed(task, "Test error")
  144. assert progress_widget._progress.failed_chapters == 1
  145. def test_multiple_chapters(self, progress_widget, sample_chapter_tasks):
  146. """Test handling multiple chapters."""
  147. notifier = ProgressNotifier()
  148. progress_widget.connect_to_notifier(notifier)
  149. notifier.notify_pipeline_start(5)
  150. # Start and complete 3 chapters
  151. for i in range(3):
  152. task = sample_chapter_tasks[i]
  153. notifier.notify_chapter_start(task)
  154. task.status = TaskStatus.COMPLETED
  155. notifier.notify_chapter_complete(task)
  156. assert len(progress_widget._chapter_items) == 3
  157. assert progress_widget._progress.completed_chapters == 3
  158. def test_stats_update(self, progress_widget):
  159. """Test statistics display updates."""
  160. notifier = ProgressNotifier()
  161. progress_widget.connect_to_notifier(notifier)
  162. notifier.notify_pipeline_start(10)
  163. progress = PipelineProgress(
  164. total_chapters=10,
  165. completed_chapters=5,
  166. failed_chapters=1,
  167. state=SchedulerState.RUNNING
  168. )
  169. progress_widget._progress = progress
  170. progress_widget._update_stats()
  171. assert progress_widget._completed_label.text() == "Completed: 5"
  172. assert progress_widget._failed_label.text() == "Failed: 1"
  173. assert progress_widget._remaining_label.text() == "Remaining: 4"
  174. def test_reset(self, progress_widget):
  175. """Test resetting the widget."""
  176. notifier = ProgressNotifier()
  177. progress_widget.connect_to_notifier(notifier)
  178. # Start and add some chapters
  179. notifier.notify_pipeline_start(5)
  180. task = ChapterTask(
  181. chapter_id="ch_0",
  182. chapter_index=0,
  183. title="Chapter 1",
  184. original_content="Content\n"
  185. )
  186. notifier.notify_chapter_start(task)
  187. assert len(progress_widget._chapter_items) == 1
  188. # Reset
  189. progress_widget.reset()
  190. assert progress_widget._progress is None
  191. assert progress_widget._start_time is None
  192. assert len(progress_widget._chapter_items) == 0
  193. assert not progress_widget._eta_timer.isActive()
  194. def test_format_time(self, progress_widget):
  195. """Test time formatting."""
  196. # Seconds only
  197. assert progress_widget._format_time(45) == "00:45"
  198. # Minutes and seconds
  199. assert progress_widget._format_time(125) == "02:05"
  200. # Hours, minutes, seconds
  201. assert progress_widget._format_time(3661) == "01:01:01"
  202. class TestChapterProgressItem:
  203. """Test ChapterProgressItem functionality."""
  204. def test_initialization(self, app):
  205. """Test chapter progress item initialization."""
  206. task = ChapterTask(
  207. chapter_id="ch_0",
  208. chapter_index=0,
  209. title="Chapter 1",
  210. original_content="Content\n"
  211. )
  212. item = ChapterProgressItem(task)
  213. assert item._task == task
  214. assert item._title_label.text() == "Chapter 1: Chapter 1"
  215. def test_status_icons(self, app):
  216. """Test status icons are correct."""
  217. task = ChapterTask(
  218. chapter_id="ch_0",
  219. chapter_index=0,
  220. title="Chapter 1",
  221. original_content="Content\n"
  222. )
  223. item = ChapterProgressItem(task)
  224. # Test different statuses
  225. for status, expected_icon in ChapterProgressItem.STATUS_ICONS.items():
  226. task.status = status
  227. item._update_style()
  228. assert item._icon_label.text() == expected_icon
  229. def test_update_task(self, app):
  230. """Test updating task in item."""
  231. task = ChapterTask(
  232. chapter_id="ch_0",
  233. chapter_index=0,
  234. title="Chapter 1",
  235. original_content="Content\n"
  236. )
  237. item = ChapterProgressItem(task)
  238. # Update task with completion
  239. task.status = TaskStatus.COMPLETED
  240. task.started_at = datetime.now()
  241. task.completed_at = datetime.now() + timedelta(seconds=10)
  242. item.update_task(task)
  243. assert item._task.status == TaskStatus.COMPLETED
  244. assert "Completed" in item._details_label.text()
  245. def test_failed_task_display(self, app):
  246. """Test failed task displays error message."""
  247. task = ChapterTask(
  248. chapter_id="ch_0",
  249. chapter_index=0,
  250. title="Chapter 1",
  251. original_content="Content\n",
  252. status=TaskStatus.FAILED,
  253. error_message="Translation failed: connection timeout"
  254. )
  255. item = ChapterProgressItem(task)
  256. assert "Failed" in item._details_label.text()
  257. assert "connection timeout" in item._details_label.text()
  258. def test_retrying_task_display(self, app):
  259. """Test retrying task displays retry count."""
  260. task = ChapterTask(
  261. chapter_id="ch_0",
  262. chapter_index=0,
  263. title="Chapter 1",
  264. original_content="Content\n",
  265. status=TaskStatus.RETRYING,
  266. retry_count=2
  267. )
  268. item = ChapterProgressItem(task)
  269. assert "Retrying" in item._details_label.text()
  270. assert "2/3" in item._details_label.text()
  271. class TestProgressIntegration:
  272. """Test ProgressWidget integration with ProgressNotifier."""
  273. def test_full_workflow(self, progress_widget, sample_chapter_tasks):
  274. """Test complete translation workflow."""
  275. notifier = ProgressNotifier()
  276. progress_widget.connect_to_notifier(notifier)
  277. # Start pipeline
  278. notifier.notify_pipeline_start(5)
  279. assert progress_widget._progress.total_chapters == 5
  280. # Process chapters
  281. for i, task in enumerate(sample_chapter_tasks):
  282. notifier.notify_chapter_start(task)
  283. # Simulate completion
  284. task.status = TaskStatus.COMPLETED
  285. task.started_at = datetime.now()
  286. task.completed_at = datetime.now() + timedelta(seconds=10)
  287. notifier.notify_chapter_complete(task)
  288. # Complete pipeline
  289. progress = progress_widget._progress
  290. notifier.notify_pipeline_complete(progress)
  291. assert progress_widget._progress_bar.value() == 100
  292. def test_error_recovery(self, progress_widget, sample_chapter_tasks):
  293. """Test error and recovery scenario."""
  294. notifier = ProgressNotifier()
  295. progress_widget.connect_to_notifier(notifier)
  296. # Start
  297. notifier.notify_pipeline_start(3)
  298. # First chapter succeeds
  299. notifier.notify_chapter_start(sample_chapter_tasks[0])
  300. sample_chapter_tasks[0].status = TaskStatus.COMPLETED
  301. notifier.notify_chapter_complete(sample_chapter_tasks[0])
  302. # Second chapter fails
  303. notifier.notify_chapter_start(sample_chapter_tasks[1])
  304. sample_chapter_tasks[1].status = TaskStatus.FAILED
  305. sample_chapter_tasks[1].error_message = "Network error"
  306. notifier.notify_chapter_failed(sample_chapter_tasks[1], "Network error")
  307. assert progress_widget._progress.completed_chapters == 1
  308. assert progress_widget._progress.failed_chapters == 1