公開前の確認用サイト
日常訳NICHIJOYAKUEnglish ↗

OPEN SOURCE

software/tests/test_tui.py

このサイトで読み、必要なファイルを保存できます。

日常訳はAIを用いて作成された翻訳です。人間の聖書学・原語の専門家による全訳の検証は確認されていません。既存の翻訳や原語資料と照らし合わせてお読みください。

← オープンソースの案内へ

software/tests/test_tui.py545 行このファイルを保存 ↓

ZIPに含まれるものと同じ公開ソースです。行番号から、その行へのリンクを保存できます。

ファイルの確認情報SHA-256 e66965dacbfaefdc9e1af49f4f4d70b2077b4eaa26aae16b26fd580c2533ec72
1"""Tests for the TUI module."""23from __future__ import annotations45import json6from datetime import datetime7from pathlib import Path8from unittest.mock import MagicMock, patch910import pytest11from rich.text import Text1213from src.models.evaluation import (14    ChapterEvaluation,15    EvaluationScore,16    VerseEvaluation,17)18from src.tui.data_provider import TUIDataProvider, VerseData19from src.tui.widgets import (20    DetailPanel,21    EvaluationSummaryTable,22    VerseSelected,23    VerseTree,24)252627@pytest.fixture28def temp_data_dir(tmp_path: Path) -> Path:29    """Create a temporary data directory with test data."""30    # Create source directories31    sources_dir = tmp_path / "sources"32    (sources_dir / "greek").mkdir(parents=True)33    (sources_dir / "english" / "kjv").mkdir(parents=True)34    (sources_dir / "japanese" / "kougo").mkdir(parents=True)3536    # Create Greek source37    greek_data = {"1:1": {"text": "Παῦλος δοῦλος θεοῦ", "translit": "Paulos doulos theou"}}38    with (sources_dir / "greek" / "titus.json").open("w") as f:39        json.dump(greek_data, f)4041    # Create KJV source42    kjv_data = {"1:1": "Paul, a servant of God, and an apostle of Jesus Christ"}43    with (sources_dir / "english" / "kjv" / "titus.json").open("w") as f:44        json.dump(kjv_data, f)4546    # Create Kougo source47    kougo_data = {"1:1": "神のしもべ、イエス・キリストの使徒パウロから"}48    with (sources_dir / "japanese" / "kougo" / "titus.json").open("w") as f:49        json.dump(kougo_data, f)5051    # Create output directory with translations52    output_dir = tmp_path / "output" / "titus"53    output_dir.mkdir(parents=True)5455    chapter_data = {56        "book": "titus",57        "chapter": 1,58        "verses": {59            "1": {60                "text": "神の僕、イエス・キリストの使徒パウロから。",61                "model": "claude-opus-4-5",62                "timestamp": "2026-01-13T00:00:00Z",63                "glossary_terms_applied": ["θεός"],64            },65            "2": {66                "text": "この信仰と知識は、永遠の命の希望に基づいています。",67                "model": "claude-opus-4-5",68                "timestamp": "2026-01-13T00:00:00Z",69                "glossary_terms_applied": [],70            },71        },72    }73    with (output_dir / "chapter_01.json").open("w") as f:74        json.dump(chapter_data, f)7576    # Create evaluation file77    eval_data = {78        "book": "titus",79        "chapter": 1,80        "evaluator_model": "claude-opus-4-5",81        "evaluated_at": "2026-01-13T00:00:00Z",82        "verses": {83            "1": {84                "scores": {85                    "theological_accuracy": {86                        "score": 95,87                        "confidence": 90,88                        "reasoning": "Excellent",89                    },90                    "source_fidelity": {91                        "score": 88,92                        "confidence": 85,93                        "reasoning": "Good",94                    },95                    "naturalness": {96                        "score": 92,97                        "confidence": 95,98                        "reasoning": "Very natural",99                    },100                    "readability": {101                        "score": 90,102                        "confidence": 92,103                        "reasoning": "Easy to read",104                    },105                    "glossary_consistency": {106                        "score": 100,107                        "confidence": 100,108                        "reasoning": "Perfect",109                    },110                    "literary_quality": {111                        "score": 85,112                        "confidence": 80,113                        "reasoning": "Good quality",114                    },115                },116                "overall_score": 91.7,117                "flags": [],118                "notes": None,119                "timestamp": "2026-01-13T00:00:00Z",120            },121            "2": {122                "scores": {123                    "theological_accuracy": {124                        "score": 70,125                        "confidence": 65,126                        "reasoning": "Some concerns",127                    },128                    "source_fidelity": {129                        "score": 65,130                        "confidence": 60,131                        "reasoning": "Could be better",132                    },133                    "naturalness": {134                        "score": 80,135                        "confidence": 85,136                        "reasoning": "OK",137                    },138                    "readability": {139                        "score": 75,140                        "confidence": 80,141                        "reasoning": "Average",142                    },143                    "glossary_consistency": {144                        "score": 90,145                        "confidence": 95,146                        "reasoning": "Good",147                    },148                    "literary_quality": {149                        "score": 70,150                        "confidence": 75,151                        "reasoning": "Could improve",152                    },153                },154                "overall_score": 75.0,155                "flags": ["theological_concern", "source_fidelity_concern"],156                "notes": "Needs review",157                "timestamp": "2026-01-13T00:00:00Z",158            },159        },160        "aggregate": {161            "scores": {162                "theological_accuracy": 82.5,163                "source_fidelity": 76.5,164            },165            "overall": 83.3,166            "flagged_count": 1,167            "flagged_verses": [2],168        },169    }170    with (output_dir / "chapter_01_eval.json").open("w") as f:171        json.dump(eval_data, f)172173    # Create metadata directory174    metadata_dir = tmp_path / "metadata"175    metadata_dir.mkdir(parents=True)176177    return tmp_path178179180class TestTUIDataProvider:181    """Tests for TUIDataProvider."""182183    def test_get_available_books(self, temp_data_dir: Path) -> None:184        """Test getting available books."""185        provider = TUIDataProvider(temp_data_dir)186        books = provider.get_available_books()187        assert books == ["titus"]188189    def test_get_available_books_empty(self, tmp_path: Path) -> None:190        """Test getting available books with no data."""191        provider = TUIDataProvider(tmp_path)192        books = provider.get_available_books()193        assert books == []194195    def test_get_book_chapters(self, temp_data_dir: Path) -> None:196        """Test getting chapters for a book."""197        provider = TUIDataProvider(temp_data_dir)198        chapters = provider.get_book_chapters("titus")199        assert chapters == [1]200201    def test_get_book_chapters_nonexistent(self, temp_data_dir: Path) -> None:202        """Test getting chapters for a nonexistent book."""203        provider = TUIDataProvider(temp_data_dir)204        chapters = provider.get_book_chapters("genesis")205        assert chapters == []206207    def test_get_chapter_verses(self, temp_data_dir: Path) -> None:208        """Test getting verses for a chapter."""209        provider = TUIDataProvider(temp_data_dir)210        verses = provider.get_chapter_verses("titus", 1)211        assert verses == [1, 2]212213    def test_get_chapter_verses_nonexistent(self, temp_data_dir: Path) -> None:214        """Test getting verses for a nonexistent chapter."""215        provider = TUIDataProvider(temp_data_dir)216        verses = provider.get_chapter_verses("titus", 99)217        assert verses == []218219    def test_get_flagged_verses(self, temp_data_dir: Path) -> None:220        """Test getting flagged verses."""221        provider = TUIDataProvider(temp_data_dir)222        flagged = provider.get_flagged_verses("titus", 1)223        assert flagged == {2}224225    def test_get_flagged_verses_no_eval(self, temp_data_dir: Path) -> None:226        """Test getting flagged verses when no evaluation exists."""227        provider = TUIDataProvider(temp_data_dir)228        flagged = provider.get_flagged_verses("titus", 99)229        assert flagged == set()230231    def test_is_chapter_evaluated(self, temp_data_dir: Path) -> None:232        """Test checking if chapter is evaluated."""233        provider = TUIDataProvider(temp_data_dir)234        assert provider.is_chapter_evaluated("titus", 1) is True235        assert provider.is_chapter_evaluated("titus", 99) is False236237    def test_get_verse_data(self, temp_data_dir: Path) -> None:238        """Test getting complete verse data."""239        provider = TUIDataProvider(temp_data_dir)240        verse_data = provider.get_verse_data("titus", 1, 1)241242        assert verse_data.book == "titus"243        assert verse_data.chapter == 1244        assert verse_data.verse == 1245        assert verse_data.greek_text == "Παῦλος δοῦλος θεοῦ"246        assert verse_data.kjv_text == "Paul, a servant of God, and an apostle of Jesus Christ"247        assert verse_data.japanese_reference == "神のしもべ、イエス・キリストの使徒パウロから"248        assert verse_data.glossary_terms == ["θεός"]249        assert verse_data.translation_text == "神の僕、イエス・キリストの使徒パウロから。"250        assert verse_data.translation_model == "claude-opus-4-5"251        assert verse_data.evaluation is not None252        assert verse_data.evaluation.overall_score == pytest.approx(91.7, rel=0.1)253254    def test_get_verse_data_no_translation(self, temp_data_dir: Path) -> None:255        """Test getting verse data when translation doesn't exist."""256        provider = TUIDataProvider(temp_data_dir)257        # Verse 3 doesn't exist in translations258        verse_data = provider.get_verse_data("titus", 1, 3)259260        assert verse_data.translation_text is None261        assert verse_data.translation_model is None262        assert verse_data.evaluation is None263264265class TestVerseSelected:266    """Tests for VerseSelected message."""267268    def test_verse_selected_message(self) -> None:269        """Test creating a verse selected message."""270        msg = VerseSelected("titus", 1, 5)271        assert msg.book == "titus"272        assert msg.chapter == 1273        assert msg.verse == 5274275276class TestDetailPanel:277    """Tests for DetailPanel widget."""278279    def _render_to_string(self, panel: DetailPanel) -> str:280        """Render panel content to a string for testing."""281        from io import StringIO282283        from rich.console import Console284285        rendered = panel.render()286        console = Console(file=StringIO(), force_terminal=True)287        console.print(rendered)288        return console.file.getvalue()289290    def test_render_no_verse(self, temp_data_dir: Path) -> None:291        """Test rendering with no verse selected."""292        provider = TUIDataProvider(temp_data_dir)293        panel = DetailPanel(provider, id="detail")294        rendered_str = self._render_to_string(panel)295        # Should show placeholder message296        assert "Select a verse" in rendered_str297298    def test_render_with_verse(self, temp_data_dir: Path) -> None:299        """Test rendering with a verse selected."""300        provider = TUIDataProvider(temp_data_dir)301        panel = DetailPanel(provider, id="detail")302        panel.update_verse("titus", 1, 1)303        rendered_str = self._render_to_string(panel)304305        # Check that key elements are present306        assert "TITUS 1:1" in rendered_str307        assert "SOURCE TEXTS" in rendered_str308        assert "TRANSLATION" in rendered_str309        assert "EVALUATION" in rendered_str310311    def test_render_with_flagged_verse(self, temp_data_dir: Path) -> None:312        """Test rendering a flagged verse."""313        provider = TUIDataProvider(temp_data_dir)314        panel = DetailPanel(provider, id="detail")315        panel.update_verse("titus", 1, 2)316        rendered_str = self._render_to_string(panel)317318        # Should show flags319        assert "theological_concern" in rendered_str or "Flags" in rendered_str320321    def test_render_verse_without_evaluation(self, temp_data_dir: Path) -> None:322        """Test rendering a verse without evaluation."""323        provider = TUIDataProvider(temp_data_dir)324        panel = DetailPanel(provider, id="detail")325        # Create a verse data without evaluation326        panel.current_verse = VerseData(327            book="titus",328            chapter=1,329            verse=99,330            greek_text=None,331            kjv_text=None,332            japanese_reference=None,333            glossary_terms=[],334            translation_text=None,335            translation_model=None,336            translation_timestamp=None,337            evaluation=None,338        )339        rendered_str = self._render_to_string(panel)340        assert "not evaluated" in rendered_str341342343class TestEvaluationSummaryTable:344    """Tests for EvaluationSummaryTable widget."""345346    def _render_to_string(self, widget: EvaluationSummaryTable) -> str:347        """Render widget content to a string for testing."""348        from io import StringIO349350        from rich.console import Console351352        rendered = widget.render()353        console = Console(file=StringIO(), force_terminal=True)354        console.print(rendered)355        return console.file.getvalue()356357    def test_render_no_scores(self) -> None:358        """Test rendering with no scores."""359        table = EvaluationSummaryTable(id="eval-table")360        rendered = table.render()361        assert isinstance(rendered, Text)362        rendered_str = self._render_to_string(table)363        assert "No evaluation" in rendered_str364365    def test_render_with_scores(self) -> None:366        """Test rendering with scores."""367        table = EvaluationSummaryTable(id="eval-table")368        table.set_scores({369            "theological_accuracy": (95.0, 90.0),370            "source_fidelity": (70.0, 85.0),371            "naturalness": (55.0, 80.0),372        })373        rendered_str = self._render_to_string(table)374        # Should contain dimension name (abbreviated names like "Theology" are also acceptable)375        assert "Theolog" in rendered_str or "theological" in rendered_str.lower()376377378class TestVerseTree:379    """Tests for VerseTree widget."""380381    def test_tree_initialization(self, temp_data_dir: Path) -> None:382        """Test tree widget initialization."""383        provider = TUIDataProvider(temp_data_dir)384        tree = VerseTree(provider, id="tree")385        assert tree.data_provider == provider386387    def test_tree_populate(self, temp_data_dir: Path) -> None:388        """Test tree population."""389        provider = TUIDataProvider(temp_data_dir)390        tree = VerseTree(provider, id="tree")391        tree._populate_tree()392393        # Check that the tree has nodes394        assert len(list(tree.root.children)) > 0395396397class TestTranslationViewerApp:398    """Tests for TranslationViewerApp."""399400    def test_app_initialization(self, temp_data_dir: Path) -> None:401        """Test app initialization."""402        from src.tui.app import TranslationViewerApp403404        app = TranslationViewerApp(data_dir=temp_data_dir)405        assert app.data_dir == temp_data_dir406        assert app.data_provider is not None407408    def test_app_has_bindings(self, temp_data_dir: Path) -> None:409        """Test app has expected bindings."""410        from src.tui.app import TranslationViewerApp411412        binding_keys = [b.key for b in TranslationViewerApp.BINDINGS]413        assert "q" in binding_keys414        assert "d" in binding_keys415        assert "r" in binding_keys416417    def test_app_css_defined(self, temp_data_dir: Path) -> None:418        """Test app has CSS defined."""419        from src.tui.app import TranslationViewerApp420421        app = TranslationViewerApp(data_dir=temp_data_dir)422        assert app.CSS is not None423        assert len(app.CSS) > 0424425426class TestMainTuiCommand:427    """Tests for the TUI command in main.py."""428429    def test_run_tui_callable(self) -> None:430        """Test run_tui is callable."""431        from src.tui.app import run_tui432433        assert callable(run_tui)434435    def test_tui_cmd_import(self) -> None:436        """Test tui_cmd can be imported from main."""437        from main import tui_cmd438439        assert callable(tui_cmd)440441442class TestDataProviderEdgeCases:443    """Tests for edge cases in TUIDataProvider."""444445    def test_get_book_chapters_invalid_filename(self, tmp_path: Path) -> None:446        """Test handling of invalid chapter filenames."""447        output_dir = tmp_path / "output" / "testbook"448        output_dir.mkdir(parents=True)449450        # Create a file with invalid name that can't be parsed451        (output_dir / "chapter_invalid.json").write_text("{}")452        (output_dir / "chapter_.json").write_text("{}")  # No number part453454        provider = TUIDataProvider(tmp_path)455        chapters = provider.get_book_chapters("testbook")456        # Should return empty list since no valid chapter numbers457        assert chapters == []458459460class TestWidgetsEdgeCases:461    """Tests for edge cases in widgets."""462463    def test_verse_tree_on_mount(self, temp_data_dir: Path) -> None:464        """Test the on_mount method populates the tree."""465        provider = TUIDataProvider(temp_data_dir)466        tree = VerseTree(provider, id="tree")467        # Manually call on_mount468        tree.on_mount()469        # Tree should be populated470        assert len(list(tree.root.children)) > 0471472473class TestAppMethods:474    """Tests for TranslationViewerApp methods."""475476    def test_app_compose_is_generator(self, temp_data_dir: Path) -> None:477        """Test that compose is a generator method."""478        from src.tui.app import TranslationViewerApp479480        app = TranslationViewerApp(data_dir=temp_data_dir)481        compose_gen = app.compose()482        # compose() returns a generator483        import types484485        assert isinstance(compose_gen, types.GeneratorType)486487    def test_app_title_and_css(self, temp_data_dir: Path) -> None:488        """Test app has title and CSS defined."""489        from src.tui.app import TranslationViewerApp490491        assert TranslationViewerApp.TITLE == "Japanese Bible Translator Viewer"492        assert TranslationViewerApp.CSS is not None493        assert "#tree-container" in TranslationViewerApp.CSS494495496class TestDetailPanelWithTranslation:497    """Additional tests for DetailPanel rendering."""498499    def _render_to_string(self, panel: DetailPanel) -> str:500        """Render panel content to a string for testing."""501        from io import StringIO502503        from rich.console import Console504505        rendered = panel.render()506        console = Console(file=StringIO(), force_terminal=True)507        console.print(rendered)508        return console.file.getvalue()509510    def test_render_verse_with_notes(self, temp_data_dir: Path) -> None:511        """Test rendering a verse with notes."""512        provider = TUIDataProvider(temp_data_dir)513        panel = DetailPanel(provider, id="detail")514515        # Create verse data with notes516        panel.current_verse = VerseData(517            book="titus",518            chapter=1,519            verse=2,520            greek_text="Test Greek",521            kjv_text="Test KJV",522            japanese_reference="Test Japanese",523            glossary_terms=["term1", "term2"],524            translation_text="Test translation",525            translation_model="test-model",526            translation_timestamp="2026-01-13",527            evaluation=VerseEvaluation(528                verse_number=2,529                evaluator_model="test-model",530                timestamp=datetime.utcnow(),531                scores={532                    "theological_accuracy": EvaluationScore(533                        dimension="theological_accuracy",534                        score=85.0,535                        confidence=90.0,536                        reasoning="Good",537                    ),538                },539                flags=["test_flag"],540                notes="This is a test note",541            ),542        )543        rendered_str = self._render_to_string(panel)544        assert "test note" in rendered_str.lower() or "Notes" in rendered_str545