OPEN SOURCE
software/tests/test_tui.py
このサイトで読み、必要なファイルを保存できます。
日常訳はAIを用いて作成された翻訳です。人間の聖書学・原語の専門家による全訳の検証は確認されていません。既存の翻訳や原語資料と照らし合わせてお読みください。
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 (14ChapterEvaluation,15EvaluationScore,16VerseEvaluation,17)18from src.tui.data_provider import TUIDataProvider, VerseData19from src.tui.widgets import (20DetailPanel,21EvaluationSummaryTable,22VerseSelected,23VerseTree,24)252627@pytest.fixture28def temp_data_dir(tmp_path: Path) -> Path:29"""Create a temporary data directory with test data."""30# Create source directories31sources_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 source37greek_data = {"1:1": {"text": "Παῦλος δοῦλος θεοῦ", "translit": "Paulos doulos theou"}}38with (sources_dir / "greek" / "titus.json").open("w") as f:39json.dump(greek_data, f)4041# Create KJV source42kjv_data = {"1:1": "Paul, a servant of God, and an apostle of Jesus Christ"}43with (sources_dir / "english" / "kjv" / "titus.json").open("w") as f:44json.dump(kjv_data, f)4546# Create Kougo source47kougo_data = {"1:1": "神のしもべ、イエス・キリストの使徒パウロから"}48with (sources_dir / "japanese" / "kougo" / "titus.json").open("w") as f:49json.dump(kougo_data, f)5051# Create output directory with translations52output_dir = tmp_path / "output" / "titus"53output_dir.mkdir(parents=True)5455chapter_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}73with (output_dir / "chapter_01.json").open("w") as f:74json.dump(chapter_data, f)7576# Create evaluation file77eval_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}170with (output_dir / "chapter_01_eval.json").open("w") as f:171json.dump(eval_data, f)172173# Create metadata directory174metadata_dir = tmp_path / "metadata"175metadata_dir.mkdir(parents=True)176177return tmp_path178179180class TestTUIDataProvider:181"""Tests for TUIDataProvider."""182183def test_get_available_books(self, temp_data_dir: Path) -> None:184"""Test getting available books."""185provider = TUIDataProvider(temp_data_dir)186books = provider.get_available_books()187assert books == ["titus"]188189def test_get_available_books_empty(self, tmp_path: Path) -> None:190"""Test getting available books with no data."""191provider = TUIDataProvider(tmp_path)192books = provider.get_available_books()193assert books == []194195def test_get_book_chapters(self, temp_data_dir: Path) -> None:196"""Test getting chapters for a book."""197provider = TUIDataProvider(temp_data_dir)198chapters = provider.get_book_chapters("titus")199assert chapters == [1]200201def test_get_book_chapters_nonexistent(self, temp_data_dir: Path) -> None:202"""Test getting chapters for a nonexistent book."""203provider = TUIDataProvider(temp_data_dir)204chapters = provider.get_book_chapters("genesis")205assert chapters == []206207def test_get_chapter_verses(self, temp_data_dir: Path) -> None:208"""Test getting verses for a chapter."""209provider = TUIDataProvider(temp_data_dir)210verses = provider.get_chapter_verses("titus", 1)211assert verses == [1, 2]212213def test_get_chapter_verses_nonexistent(self, temp_data_dir: Path) -> None:214"""Test getting verses for a nonexistent chapter."""215provider = TUIDataProvider(temp_data_dir)216verses = provider.get_chapter_verses("titus", 99)217assert verses == []218219def test_get_flagged_verses(self, temp_data_dir: Path) -> None:220"""Test getting flagged verses."""221provider = TUIDataProvider(temp_data_dir)222flagged = provider.get_flagged_verses("titus", 1)223assert flagged == {2}224225def test_get_flagged_verses_no_eval(self, temp_data_dir: Path) -> None:226"""Test getting flagged verses when no evaluation exists."""227provider = TUIDataProvider(temp_data_dir)228flagged = provider.get_flagged_verses("titus", 99)229assert flagged == set()230231def test_is_chapter_evaluated(self, temp_data_dir: Path) -> None:232"""Test checking if chapter is evaluated."""233provider = TUIDataProvider(temp_data_dir)234assert provider.is_chapter_evaluated("titus", 1) is True235assert provider.is_chapter_evaluated("titus", 99) is False236237def test_get_verse_data(self, temp_data_dir: Path) -> None:238"""Test getting complete verse data."""239provider = TUIDataProvider(temp_data_dir)240verse_data = provider.get_verse_data("titus", 1, 1)241242assert verse_data.book == "titus"243assert verse_data.chapter == 1244assert verse_data.verse == 1245assert verse_data.greek_text == "Παῦλος δοῦλος θεοῦ"246assert verse_data.kjv_text == "Paul, a servant of God, and an apostle of Jesus Christ"247assert verse_data.japanese_reference == "神のしもべ、イエス・キリストの使徒パウロから"248assert verse_data.glossary_terms == ["θεός"]249assert verse_data.translation_text == "神の僕、イエス・キリストの使徒パウロから。"250assert verse_data.translation_model == "claude-opus-4-5"251assert verse_data.evaluation is not None252assert verse_data.evaluation.overall_score == pytest.approx(91.7, rel=0.1)253254def test_get_verse_data_no_translation(self, temp_data_dir: Path) -> None:255"""Test getting verse data when translation doesn't exist."""256provider = TUIDataProvider(temp_data_dir)257# Verse 3 doesn't exist in translations258verse_data = provider.get_verse_data("titus", 1, 3)259260assert verse_data.translation_text is None261assert verse_data.translation_model is None262assert verse_data.evaluation is None263264265class TestVerseSelected:266"""Tests for VerseSelected message."""267268def test_verse_selected_message(self) -> None:269"""Test creating a verse selected message."""270msg = VerseSelected("titus", 1, 5)271assert msg.book == "titus"272assert msg.chapter == 1273assert msg.verse == 5274275276class TestDetailPanel:277"""Tests for DetailPanel widget."""278279def _render_to_string(self, panel: DetailPanel) -> str:280"""Render panel content to a string for testing."""281from io import StringIO282283from rich.console import Console284285rendered = panel.render()286console = Console(file=StringIO(), force_terminal=True)287console.print(rendered)288return console.file.getvalue()289290def test_render_no_verse(self, temp_data_dir: Path) -> None:291"""Test rendering with no verse selected."""292provider = TUIDataProvider(temp_data_dir)293panel = DetailPanel(provider, id="detail")294rendered_str = self._render_to_string(panel)295# Should show placeholder message296assert "Select a verse" in rendered_str297298def test_render_with_verse(self, temp_data_dir: Path) -> None:299"""Test rendering with a verse selected."""300provider = TUIDataProvider(temp_data_dir)301panel = DetailPanel(provider, id="detail")302panel.update_verse("titus", 1, 1)303rendered_str = self._render_to_string(panel)304305# Check that key elements are present306assert "TITUS 1:1" in rendered_str307assert "SOURCE TEXTS" in rendered_str308assert "TRANSLATION" in rendered_str309assert "EVALUATION" in rendered_str310311def test_render_with_flagged_verse(self, temp_data_dir: Path) -> None:312"""Test rendering a flagged verse."""313provider = TUIDataProvider(temp_data_dir)314panel = DetailPanel(provider, id="detail")315panel.update_verse("titus", 1, 2)316rendered_str = self._render_to_string(panel)317318# Should show flags319assert "theological_concern" in rendered_str or "Flags" in rendered_str320321def test_render_verse_without_evaluation(self, temp_data_dir: Path) -> None:322"""Test rendering a verse without evaluation."""323provider = TUIDataProvider(temp_data_dir)324panel = DetailPanel(provider, id="detail")325# Create a verse data without evaluation326panel.current_verse = VerseData(327book="titus",328chapter=1,329verse=99,330greek_text=None,331kjv_text=None,332japanese_reference=None,333glossary_terms=[],334translation_text=None,335translation_model=None,336translation_timestamp=None,337evaluation=None,338)339rendered_str = self._render_to_string(panel)340assert "not evaluated" in rendered_str341342343class TestEvaluationSummaryTable:344"""Tests for EvaluationSummaryTable widget."""345346def _render_to_string(self, widget: EvaluationSummaryTable) -> str:347"""Render widget content to a string for testing."""348from io import StringIO349350from rich.console import Console351352rendered = widget.render()353console = Console(file=StringIO(), force_terminal=True)354console.print(rendered)355return console.file.getvalue()356357def test_render_no_scores(self) -> None:358"""Test rendering with no scores."""359table = EvaluationSummaryTable(id="eval-table")360rendered = table.render()361assert isinstance(rendered, Text)362rendered_str = self._render_to_string(table)363assert "No evaluation" in rendered_str364365def test_render_with_scores(self) -> None:366"""Test rendering with scores."""367table = EvaluationSummaryTable(id="eval-table")368table.set_scores({369"theological_accuracy": (95.0, 90.0),370"source_fidelity": (70.0, 85.0),371"naturalness": (55.0, 80.0),372})373rendered_str = self._render_to_string(table)374# Should contain dimension name (abbreviated names like "Theology" are also acceptable)375assert "Theolog" in rendered_str or "theological" in rendered_str.lower()376377378class TestVerseTree:379"""Tests for VerseTree widget."""380381def test_tree_initialization(self, temp_data_dir: Path) -> None:382"""Test tree widget initialization."""383provider = TUIDataProvider(temp_data_dir)384tree = VerseTree(provider, id="tree")385assert tree.data_provider == provider386387def test_tree_populate(self, temp_data_dir: Path) -> None:388"""Test tree population."""389provider = TUIDataProvider(temp_data_dir)390tree = VerseTree(provider, id="tree")391tree._populate_tree()392393# Check that the tree has nodes394assert len(list(tree.root.children)) > 0395396397class TestTranslationViewerApp:398"""Tests for TranslationViewerApp."""399400def test_app_initialization(self, temp_data_dir: Path) -> None:401"""Test app initialization."""402from src.tui.app import TranslationViewerApp403404app = TranslationViewerApp(data_dir=temp_data_dir)405assert app.data_dir == temp_data_dir406assert app.data_provider is not None407408def test_app_has_bindings(self, temp_data_dir: Path) -> None:409"""Test app has expected bindings."""410from src.tui.app import TranslationViewerApp411412binding_keys = [b.key for b in TranslationViewerApp.BINDINGS]413assert "q" in binding_keys414assert "d" in binding_keys415assert "r" in binding_keys416417def test_app_css_defined(self, temp_data_dir: Path) -> None:418"""Test app has CSS defined."""419from src.tui.app import TranslationViewerApp420421app = TranslationViewerApp(data_dir=temp_data_dir)422assert app.CSS is not None423assert len(app.CSS) > 0424425426class TestMainTuiCommand:427"""Tests for the TUI command in main.py."""428429def test_run_tui_callable(self) -> None:430"""Test run_tui is callable."""431from src.tui.app import run_tui432433assert callable(run_tui)434435def test_tui_cmd_import(self) -> None:436"""Test tui_cmd can be imported from main."""437from main import tui_cmd438439assert callable(tui_cmd)440441442class TestDataProviderEdgeCases:443"""Tests for edge cases in TUIDataProvider."""444445def test_get_book_chapters_invalid_filename(self, tmp_path: Path) -> None:446"""Test handling of invalid chapter filenames."""447output_dir = tmp_path / "output" / "testbook"448output_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 part453454provider = TUIDataProvider(tmp_path)455chapters = provider.get_book_chapters("testbook")456# Should return empty list since no valid chapter numbers457assert chapters == []458459460class TestWidgetsEdgeCases:461"""Tests for edge cases in widgets."""462463def test_verse_tree_on_mount(self, temp_data_dir: Path) -> None:464"""Test the on_mount method populates the tree."""465provider = TUIDataProvider(temp_data_dir)466tree = VerseTree(provider, id="tree")467# Manually call on_mount468tree.on_mount()469# Tree should be populated470assert len(list(tree.root.children)) > 0471472473class TestAppMethods:474"""Tests for TranslationViewerApp methods."""475476def test_app_compose_is_generator(self, temp_data_dir: Path) -> None:477"""Test that compose is a generator method."""478from src.tui.app import TranslationViewerApp479480app = TranslationViewerApp(data_dir=temp_data_dir)481compose_gen = app.compose()482# compose() returns a generator483import types484485assert isinstance(compose_gen, types.GeneratorType)486487def test_app_title_and_css(self, temp_data_dir: Path) -> None:488"""Test app has title and CSS defined."""489from src.tui.app import TranslationViewerApp490491assert TranslationViewerApp.TITLE == "Japanese Bible Translator Viewer"492assert TranslationViewerApp.CSS is not None493assert "#tree-container" in TranslationViewerApp.CSS494495496class TestDetailPanelWithTranslation:497"""Additional tests for DetailPanel rendering."""498499def _render_to_string(self, panel: DetailPanel) -> str:500"""Render panel content to a string for testing."""501from io import StringIO502503from rich.console import Console504505rendered = panel.render()506console = Console(file=StringIO(), force_terminal=True)507console.print(rendered)508return console.file.getvalue()509510def test_render_verse_with_notes(self, temp_data_dir: Path) -> None:511"""Test rendering a verse with notes."""512provider = TUIDataProvider(temp_data_dir)513panel = DetailPanel(provider, id="detail")514515# Create verse data with notes516panel.current_verse = VerseData(517book="titus",518chapter=1,519verse=2,520greek_text="Test Greek",521kjv_text="Test KJV",522japanese_reference="Test Japanese",523glossary_terms=["term1", "term2"],524translation_text="Test translation",525translation_model="test-model",526translation_timestamp="2026-01-13",527evaluation=VerseEvaluation(528verse_number=2,529evaluator_model="test-model",530timestamp=datetime.utcnow(),531scores={532"theological_accuracy": EvaluationScore(533dimension="theological_accuracy",534score=85.0,535confidence=90.0,536reasoning="Good",537),538},539flags=["test_flag"],540notes="This is a test note",541),542)543rendered_str = self._render_to_string(panel)544assert "test note" in rendered_str.lower() or "Notes" in rendered_str545