test_stats_panel.py 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263
  1. """
  2. Tests for the Statistics Panel UI component (Story 7.23).
  3. """
  4. import pytest
  5. from datetime import datetime, timedelta
  6. from typing import List, Tuple
  7. from PyQt6.QtWidgets import QApplication
  8. from src.ui.stats_panel import (
  9. ChartType,
  10. TimeRange,
  11. DailyStats,
  12. GlossaryUsage,
  13. StatisticsData,
  14. StatisticsPanel,
  15. MATPLOTLIB_AVAILABLE,
  16. )
  17. @pytest.fixture
  18. def app(qtbot):
  19. """Create QApplication for tests."""
  20. return QApplication.instance() or QApplication([])
  21. @pytest.fixture
  22. def sample_stats_data():
  23. """Create sample statistics data."""
  24. data = StatisticsData()
  25. # Basic counts
  26. data.total_words = 500000
  27. data.translated_words = 225000
  28. data.remaining_words = 275000
  29. data.total_chapters = 100
  30. data.completed_chapters = 45
  31. data.failed_chapters = 2
  32. # Daily stats
  33. base_date = datetime.now()
  34. for i in range(30):
  35. data.daily_stats.append(DailyStats(
  36. date=base_date - timedelta(days=29-i),
  37. words_translated=7000 + i * 200,
  38. chapters_completed=3 + i // 10,
  39. files_completed=1,
  40. errors_count=0 if i % 5 != 0 else 1,
  41. work_time_minutes=120
  42. ))
  43. # Speed history
  44. for i in range(100):
  45. data.speed_history.append((
  46. base_date - timedelta(minutes=100-i),
  47. 500 + i * 2 + (i % 10) * 10
  48. ))
  49. # Error counts
  50. data.error_counts = {
  51. "NetworkError": 5,
  52. "ParseError": 3,
  53. "FileError": 2,
  54. "UnknownError": 1
  55. }
  56. # Glossary usage
  57. for i in range(20):
  58. data.glossary_usage.append(GlossaryUsage(
  59. term=f"Term{i}",
  60. source=f"术语{i}",
  61. target=f"Term{i} EN",
  62. usage_count=100 - i * 4,
  63. category="character" if i % 2 == 0 else "skill"
  64. ))
  65. # Time tracking
  66. data.total_time_hours = 120
  67. data.average_wpm = 520
  68. return data
  69. class TestChartType:
  70. """Tests for ChartType enum."""
  71. def test_type_values(self):
  72. """Test ChartType enum values."""
  73. assert ChartType.PIE_PROGRESS.value == "pie_progress"
  74. assert ChartType.BAR_DAILY.value == "bar_daily"
  75. assert ChartType.LINE_SPEED.value == "line_speed"
  76. assert ChartType.BAR_ERRORS.value == "bar_errors"
  77. assert ChartType.BAR_GLOSSARY.value == "bar_glossary"
  78. class TestTimeRange:
  79. """Tests for TimeRange enum."""
  80. def test_range_values(self):
  81. """Test TimeRange enum values."""
  82. assert TimeRange.TODAY.value == "today"
  83. assert TimeRange.WEEK.value == "week"
  84. assert TimeRange.MONTH.value == "month"
  85. assert TimeRange.ALL.value == "all"
  86. class TestDailyStats:
  87. """Tests for DailyStats."""
  88. def test_creation(self):
  89. """Test creating DailyStats."""
  90. stats = DailyStats(
  91. date=datetime.now(),
  92. words_translated=10000,
  93. chapters_completed=5,
  94. files_completed=2,
  95. errors_count=0,
  96. work_time_minutes=120
  97. )
  98. assert stats.words_translated == 10000
  99. assert stats.chapters_completed == 5
  100. class TestGlossaryUsage:
  101. """Tests for GlossaryUsage."""
  102. def test_creation(self):
  103. """Test creating GlossaryUsage."""
  104. usage = GlossaryUsage(
  105. term="test",
  106. source="测试",
  107. target="test_en",
  108. usage_count=50,
  109. category="character"
  110. )
  111. assert usage.term == "test"
  112. assert usage.usage_count == 50
  113. class TestStatisticsData:
  114. """Tests for StatisticsData."""
  115. def test_default_values(self):
  116. """Test default StatisticsData values."""
  117. data = StatisticsData()
  118. assert data.total_words == 0
  119. assert data.translated_words == 0
  120. assert data.remaining_words == 0
  121. assert isinstance(data.daily_stats, list)
  122. assert isinstance(data.speed_history, list)
  123. assert isinstance(data.error_counts, dict)
  124. assert isinstance(data.glossary_usage, list)
  125. @pytest.mark.skipif(not MATPLOTLIB_AVAILABLE, reason="Matplotlib not available")
  126. class TestStatisticsPanel:
  127. """Tests for StatisticsPanel widget."""
  128. def test_initialization(self, qtbot):
  129. """Test panel initialization."""
  130. panel = StatisticsPanel()
  131. qtbot.addWidget(panel)
  132. assert panel is not None
  133. def test_set_data(self, qtbot, sample_stats_data):
  134. """Test setting statistics data."""
  135. panel = StatisticsPanel()
  136. qtbot.addWidget(panel)
  137. panel.set_data(sample_stats_data)
  138. assert panel._data == sample_stats_data
  139. def test_time_range_filter(self, qtbot, sample_stats_data):
  140. """Test time range filtering."""
  141. panel = StatisticsPanel()
  142. qtbot.addWidget(panel)
  143. panel.set_data(sample_stats_data)
  144. # Test ALL (default)
  145. assert panel._current_time_range == TimeRange.ALL
  146. # Test WEEK
  147. panel._current_time_range = TimeRange.WEEK
  148. filtered = panel._filter_by_time_range(sample_stats_data.daily_stats)
  149. assert len(filtered) <= 7 # At most 7 days in a week
  150. def test_summary_cards_update(self, qtbot, sample_stats_data):
  151. """Test summary card updates."""
  152. panel = StatisticsPanel()
  153. qtbot.addWidget(panel)
  154. panel.set_data(sample_stats_data)
  155. panel._update_summary_cards()
  156. # Check values are displayed
  157. total_text = panel._total_words_label.value_label.text()
  158. assert "500,000" in total_text or "500000" in total_text
  159. def test_refresh(self, qtbot, sample_stats_data):
  160. """Test refresh method."""
  161. panel = StatisticsPanel()
  162. qtbot.addWidget(panel)
  163. panel.set_data(sample_stats_data)
  164. # Should not raise
  165. panel.refresh()
  166. class TestStatisticsDialog:
  167. """Tests for StatisticsDialog."""
  168. def test_initialization_without_data(self, qtbot):
  169. """Test dialog initialization without data."""
  170. from src.ui.stats_panel import StatisticsDialog
  171. dialog = StatisticsDialog()
  172. qtbot.addWidget(dialog)
  173. assert dialog is not None
  174. def test_initialization_with_data(self, qtbot, sample_stats_data):
  175. """Test dialog initialization with data."""
  176. from src.ui.stats_panel import StatisticsDialog
  177. dialog = StatisticsDialog(sample_stats_data)
  178. qtbot.addWidget(dialog)
  179. assert dialog is not None
  180. def test_set_data(self, qtbot, sample_stats_data):
  181. """Test setting data in dialog."""
  182. from src.ui.stats_panel import StatisticsDialog
  183. dialog = StatisticsDialog()
  184. qtbot.addWidget(dialog)
  185. dialog.set_data(sample_stats_data)
  186. assert dialog._panel._data == sample_stats_data
  187. def test_refresh(self, qtbot, sample_stats_data):
  188. """Test refresh in dialog."""
  189. from src.ui.stats_panel import StatisticsDialog
  190. dialog = StatisticsDialog(sample_stats_data)
  191. qtbot.addWidget(dialog)
  192. # Should not raise
  193. dialog.refresh()
  194. class TestMatplotlibAvailability:
  195. """Tests for matplotlib availability check."""
  196. def test_matplotlib_flag_exists(self):
  197. """Test that MATPLOTLIB_AVAILABLE is defined."""
  198. assert isinstance(MATPLOTLIB_AVAILABLE, bool)