test_report_exporter.py 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313
  1. """
  2. Tests for the Report Exporter UI component (Story 7.17).
  3. """
  4. import pytest
  5. from pathlib import Path
  6. import tempfile
  7. import json
  8. from datetime import datetime, timedelta
  9. from PyQt6.QtWidgets import QApplication
  10. from src.ui.report_exporter import (
  11. ReportFormat,
  12. ReportSection,
  13. TranslationStatistics,
  14. ErrorEntry,
  15. ChapterProgress,
  16. ReportData,
  17. ReportExporter,
  18. DEFAULT_HTML_TEMPLATE,
  19. )
  20. @pytest.fixture
  21. def app(qtbot):
  22. """Create QApplication for tests."""
  23. return QApplication.instance() or QApplication([])
  24. @pytest.fixture
  25. def sample_statistics():
  26. """Create sample statistics."""
  27. stats = TranslationStatistics()
  28. stats.total_files = 10
  29. stats.completed_files = 5
  30. stats.pending_files = 4
  31. stats.failed_files = 1
  32. stats.total_chapters = 100
  33. stats.completed_chapters = 45
  34. stats.failed_chapters = 1
  35. stats.pending_chapters = 54
  36. stats.total_words = 500000
  37. stats.translated_words = 225000
  38. stats.remaining_words = 275000
  39. stats.elapsed_time_seconds = 7200 # 2 hours
  40. stats.estimated_remaining_seconds = 7200
  41. stats.words_per_minute = 520.0
  42. stats.chapters_per_hour = 22.5
  43. stats.total_errors = 3
  44. stats.error_types = {"NetworkError": 2, "ParseError": 1}
  45. stats.glossary_terms_used = 50
  46. stats.glossary_hit_count = 150
  47. return stats
  48. @pytest.fixture
  49. def sample_report_data(sample_statistics):
  50. """Create sample report data."""
  51. data = ReportData()
  52. data.title = "测试翻译报告"
  53. data.project_name = "测试项目"
  54. data.statistics = sample_statistics
  55. # Add chapter progress
  56. for i in range(5):
  57. data.chapter_progress.append(ChapterProgress(
  58. file_name=f"file_{i}.txt",
  59. chapter_name=f"Chapter {i+1}",
  60. status="completed" if i < 3 else "pending",
  61. word_count=5000,
  62. translated_words=5000 if i < 3 else 0,
  63. start_time=datetime.now() - timedelta(hours=2-i),
  64. end_time=datetime.now() - timedelta(hours=2-i-0.5) if i < 3 else None
  65. ))
  66. # Add errors
  67. data.errors = [
  68. ErrorEntry(
  69. timestamp=datetime.now() - timedelta(minutes=30),
  70. file="file_4.txt",
  71. chapter="Chapter 5",
  72. error_type="NetworkError",
  73. message="Connection timeout"
  74. )
  75. ]
  76. # Add daily progress
  77. for i in range(7):
  78. date = datetime.now() - timedelta(days=6-i)
  79. data.daily_progress[date.strftime("%Y-%m-%d")] = 30000 + i * 5000
  80. data.notes = "这是测试备注"
  81. return data
  82. class TestReportFormat:
  83. """Tests for ReportFormat enum."""
  84. def test_format_values(self):
  85. """Test ReportFormat enum values."""
  86. assert ReportFormat.HTML.value == "html"
  87. assert ReportFormat.PDF.value == "pdf"
  88. assert ReportFormat.JSON.value == "json"
  89. class TestTranslationStatistics:
  90. """Tests for TranslationStatistics."""
  91. def test_completion_percentage(self, sample_statistics):
  92. """Test completion percentage calculation."""
  93. assert sample_statistics.completion_percentage == 45.0
  94. def test_completion_percentage_zero_total(self):
  95. """Test completion with zero total words."""
  96. stats = TranslationStatistics()
  97. assert stats.completion_percentage == 0.0
  98. def test_formatted_elapsed_time(self, sample_statistics):
  99. """Test elapsed time formatting."""
  100. assert "2小时" in sample_statistics.formatted_elapsed_time
  101. def test_formatted_eta(self, sample_statistics):
  102. """Test ETA formatting."""
  103. assert "2小时" in sample_statistics.formatted_eta
  104. def test_formatted_eta_negative(self):
  105. """Test ETA with negative value."""
  106. stats = TranslationStatistics()
  107. stats.estimated_remaining_seconds = -1
  108. assert "计算中" in stats.formatted_eta
  109. class TestChapterProgress:
  110. """Tests for ChapterProgress."""
  111. def test_completion_percentage(self):
  112. """Test chapter completion calculation."""
  113. progress = ChapterProgress(
  114. file_name="test.txt",
  115. chapter_name="Chapter 1",
  116. status="completed",
  117. word_count=5000,
  118. translated_words=5000
  119. )
  120. assert progress.completion_percentage == 100.0
  121. def test_partial_completion(self):
  122. """Test partial chapter completion."""
  123. progress = ChapterProgress(
  124. file_name="test.txt",
  125. chapter_name="Chapter 1",
  126. status="pending",
  127. word_count=5000,
  128. translated_words=2500
  129. )
  130. assert progress.completion_percentage == 50.0
  131. def test_zero_word_count(self):
  132. """Test completion with zero word count."""
  133. progress = ChapterProgress(
  134. file_name="test.txt",
  135. chapter_name="Chapter 1",
  136. status="pending",
  137. word_count=0,
  138. translated_words=0
  139. )
  140. assert progress.completion_percentage == 0.0
  141. class TestReportExporter:
  142. """Tests for ReportExporter."""
  143. def test_initialization(self, app):
  144. """Test exporter initialization."""
  145. exporter = ReportExporter()
  146. assert exporter is not None
  147. def test_export_html(self, app, sample_report_data, tmp_path):
  148. """Test HTML export."""
  149. exporter = ReportExporter()
  150. output_path = tmp_path / "test_report.html"
  151. result = exporter.export_html(sample_report_data, output_path)
  152. assert result is True
  153. assert output_path.exists()
  154. # Verify content
  155. content = output_path.read_text(encoding="utf-8")
  156. assert sample_report_data.title in content
  157. assert sample_report_data.project_name in content
  158. assert str(sample_report_data.statistics.translated_words) in content
  159. def test_export_json(self, app, sample_report_data, tmp_path):
  160. """Test JSON export."""
  161. exporter = ReportExporter()
  162. output_path = tmp_path / "test_report.json"
  163. result = exporter.export_json(sample_report_data, output_path)
  164. assert result is True
  165. assert output_path.exists()
  166. # Verify JSON content
  167. with open(output_path, "r", encoding="utf-8") as f:
  168. data = json.load(f)
  169. assert data["title"] == sample_report_data.title
  170. assert data["project_name"] == sample_report_data.project_name
  171. assert data["statistics"]["total_words"] == sample_report_data.statistics.total_words
  172. def test_export_method(self, app, sample_report_data, tmp_path):
  173. """Test the export() method with different formats."""
  174. exporter = ReportExporter()
  175. # Test HTML export
  176. html_path = tmp_path / "report.html"
  177. result = exporter.export(sample_report_data, ReportFormat.HTML, html_path)
  178. assert result is True
  179. assert html_path.exists()
  180. # Test JSON export
  181. json_path = tmp_path / "report.json"
  182. result = exporter.export(sample_report_data, ReportFormat.JSON, json_path)
  183. assert result is True
  184. assert json_path.exists()
  185. def test_set_template(self, app):
  186. """Test setting custom template."""
  187. exporter = ReportExporter()
  188. custom_template = "<html>{{title}}</html>"
  189. exporter.set_template(custom_template)
  190. assert exporter._template == custom_template
  191. def test_load_template_from_file(self, app, sample_report_data, tmp_path):
  192. """Test loading template from file."""
  193. template_path = tmp_path / "template.html"
  194. template_path.write_text("<html>{{title}}</html>", encoding="utf-8")
  195. exporter = ReportExporter()
  196. result = exporter.load_template_from_file(template_path)
  197. assert result is True
  198. assert exporter._template == "<html>{{title}}</html>"
  199. def test_load_nonexistent_template(self, app, tmp_path):
  200. """Test loading non-existent template file."""
  201. exporter = ReportExporter()
  202. result = exporter.load_template_from_file(tmp_path / "nonexistent.html")
  203. assert result is False
  204. class TestReportData:
  205. """Tests for ReportData."""
  206. def test_default_values(self):
  207. """Test default ReportData values."""
  208. data = ReportData()
  209. assert data.title == "翻译报告"
  210. assert data.project_name == ""
  211. assert data.source_language == "中文"
  212. assert data.target_language == "英文"
  213. assert isinstance(data.statistics, TranslationStatistics)
  214. assert isinstance(data.chapter_progress, list)
  215. assert isinstance(data.errors, list)
  216. def test_custom_values(self):
  217. """Test ReportData with custom values."""
  218. data = ReportData(
  219. title="Custom Title",
  220. project_name="My Project",
  221. source_language="Japanese",
  222. target_language="English"
  223. )
  224. assert data.title == "Custom Title"
  225. assert data.project_name == "My Project"
  226. assert data.source_language == "Japanese"
  227. assert data.target_language == "English"
  228. class TestHtmlTemplate:
  229. """Tests for HTML template."""
  230. def test_template_structure(self):
  231. """Test that template has required structure."""
  232. assert "{{title}}" in DEFAULT_HTML_TEMPLATE
  233. assert "{{generated_at}}" in DEFAULT_HTML_TEMPLATE
  234. assert "{{completion_percentage}}" in DEFAULT_HTML_TEMPLATE
  235. assert "{{translated_words}}" in DEFAULT_HTML_TEMPLATE
  236. def test_template_has_styles(self):
  237. """Test that template includes CSS styles."""
  238. assert "<style>" in DEFAULT_HTML_TEMPLATE
  239. assert "</style>" in DEFAULT_HTML_TEMPLATE
  240. def test_template_has_charts_section(self):
  241. """Test that template has sections for charts."""
  242. assert "翻译概览" in DEFAULT_HTML_TEMPLATE
  243. assert "详细统计" in DEFAULT_HTML_TEMPLATE
  244. assert "章节进度" in DEFAULT_HTML_TEMPLATE