"""Tests for output formatters.""" import json import pytest from pathlib import Path from src.data.storage import Storage from src.models.verse import BookMetadata, TranslationOutput, VerseReference from src.output.formatters import OutputFormatter @pytest.fixture def temp_data_dir(tmp_path: Path) -> Path: """Create temporary data directory with translations.""" (tmp_path / "output" / "john").mkdir(parents=True) (tmp_path / "progress").mkdir(parents=True) return tmp_path @pytest.fixture def storage_with_translations(temp_data_dir: Path) -> Storage: """Create storage with test translations.""" storage = Storage(temp_data_dir) # Add translations for John chapter 1 for verse_num in range(1, 6): ref = VerseReference(book="john", chapter=1, verse=verse_num) output = TranslationOutput( reference=ref, text=f"ヨハネ第1章{verse_num}節の翻訳。", model="test-model", ) storage.save_translation(output) # Add translations for John chapter 2 for verse_num in range(1, 4): ref = VerseReference(book="john", chapter=2, verse=verse_num) output = TranslationOutput( reference=ref, text=f"ヨハネ第2章{verse_num}節の翻訳。", model="test-model", ) storage.save_translation(output) return storage class TestOutputFormatter: """Tests for OutputFormatter.""" def test_export_chapter_json( self, storage_with_translations: Storage, tmp_path: Path ) -> None: """Test exporting chapter as JSON.""" formatter = OutputFormatter(storage_with_translations) output_dir = tmp_path / "exports" path = formatter.export_chapter_json("john", 1, output_dir) assert path.exists() with path.open("r", encoding="utf-8") as f: data = json.load(f) assert data["book"] == "john" assert data["chapter"] == 1 assert len(data["verses"]) == 5 def test_export_chapter_text( self, storage_with_translations: Storage, tmp_path: Path ) -> None: """Test exporting chapter as plain text.""" formatter = OutputFormatter(storage_with_translations) output_dir = tmp_path / "exports" path = formatter.export_chapter_text("john", 1, output_dir) assert path.exists() content = path.read_text(encoding="utf-8") assert "John Chapter 1" in content assert "ヨハネ第1章1節の翻訳。" in content def test_export_chapter_html( self, storage_with_translations: Storage, tmp_path: Path ) -> None: """Test exporting chapter as HTML.""" formatter = OutputFormatter(storage_with_translations) output_dir = tmp_path / "exports" path = formatter.export_chapter_html("john", 1, output_dir) assert path.exists() content = path.read_text(encoding="utf-8") assert "" in content assert '' in content assert "ヨハネ第1章1節の翻訳。" in content def test_export_chapter_html_with_metadata( self, storage_with_translations: Storage, tmp_path: Path ) -> None: """Test exporting chapter HTML with metadata.""" formatter = OutputFormatter(storage_with_translations) output_dir = tmp_path / "exports" metadata = BookMetadata( book="john", name_japanese="ヨハネによる福音書", name_english="Gospel of John", testament="NT", author="John", themes=[], description="Test", chapter_count=21, verse_count=879, ) path = formatter.export_chapter_html("john", 1, output_dir, metadata) content = path.read_text(encoding="utf-8") assert "ヨハネによる福音書" in content assert "第1章" in content def test_export_book_json( self, storage_with_translations: Storage, tmp_path: Path ) -> None: """Test exporting entire book as JSON.""" formatter = OutputFormatter(storage_with_translations) output_dir = tmp_path / "exports" path = formatter.export_book_json("john", output_dir) assert path.exists() with path.open("r", encoding="utf-8") as f: data = json.load(f) assert data["book"] == "john" assert "1" in data["chapters"] assert "2" in data["chapters"] def test_export_book_text( self, storage_with_translations: Storage, tmp_path: Path ) -> None: """Test exporting entire book as plain text.""" formatter = OutputFormatter(storage_with_translations) output_dir = tmp_path / "exports" path = formatter.export_book_text("john", output_dir) assert path.exists() content = path.read_text(encoding="utf-8") assert "John" in content assert "Chapter 1" in content assert "Chapter 2" in content def test_export_book_json_empty( self, temp_data_dir: Path, tmp_path: Path ) -> None: """Test exporting book with no translations.""" storage = Storage(temp_data_dir) formatter = OutputFormatter(storage) output_dir = tmp_path / "exports" path = formatter.export_book_json("matthew", output_dir) assert path.exists() with path.open("r", encoding="utf-8") as f: data = json.load(f) assert data["book"] == "matthew" assert data["chapters"] == {} def test_export_book_text_empty( self, temp_data_dir: Path, tmp_path: Path ) -> None: """Test exporting empty book as text.""" storage = Storage(temp_data_dir) formatter = OutputFormatter(storage) output_dir = tmp_path / "exports" path = formatter.export_book_text("matthew", output_dir) assert path.exists() content = path.read_text(encoding="utf-8") assert "Matthew" in content def test_export_creates_directory( self, storage_with_translations: Storage, tmp_path: Path ) -> None: """Test export creates output directory if needed.""" formatter = OutputFormatter(storage_with_translations) output_dir = tmp_path / "nested" / "deep" / "exports" path = formatter.export_chapter_json("john", 1, output_dir) assert path.exists() assert output_dir.exists() def test_export_chapter_html_with_furigana( self, temp_data_dir: Path, tmp_path: Path ) -> None: """Test exporting chapter HTML includes ruby tags for furigana.""" storage = Storage(temp_data_dir) # Create translation with furigana ref = VerseReference(book="john", chapter=1, verse=1) output = TranslationOutput( reference=ref, text="神は愛です", text_with_furigana="神[かみ]は愛[あい]です", furigana_annotations=[ { "start_offset": 0, "end_offset": 1, "base_text": "神", "reading": "かみ", }, { "start_offset": 2, "end_offset": 3, "base_text": "愛", "reading": "あい", }, ], model="test-model", ) storage.save_translation(output) formatter = OutputFormatter(storage) output_dir = tmp_path / "exports" path = formatter.export_chapter_html("john", 1, output_dir) content = path.read_text(encoding="utf-8") # Check ruby tags are present assert "神かみ" in content assert "愛あい" in content def test_export_chapter_html_includes_ruby_css( self, storage_with_translations: Storage, tmp_path: Path ) -> None: """Test HTML export includes CSS for ruby styling.""" formatter = OutputFormatter(storage_with_translations) output_dir = tmp_path / "exports" path = formatter.export_chapter_html("john", 1, output_dir) content = path.read_text(encoding="utf-8") assert "ruby { ruby-position: over; }" in content assert "rt { font-size: 0.6em; color: #666; }" in content def test_export_chapter_html_fallback_without_furigana( self, storage_with_translations: Storage, tmp_path: Path ) -> None: """Test HTML export falls back to plain text without furigana.""" formatter = OutputFormatter(storage_with_translations) output_dir = tmp_path / "exports" path = formatter.export_chapter_html("john", 1, output_dir) content = path.read_text(encoding="utf-8") # Original test translations don't have furigana, should use plain text assert "ヨハネ第1章1節の翻訳。" in content # Should not have any ruby tags for these translations assert "" not in content or "ヨハネ" not in content.split("")[0]