| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290 |
- """
- Tests for the Glossary Editor UI component (Story 7.19).
- """
- import pytest
- from pathlib import Path
- import tempfile
- import json
- from PyQt6.QtWidgets import QApplication
- from PyQt6.QtCore import Qt
- from src.glossary.models import Glossary, GlossaryEntry, TermCategory
- from src.ui.glossary_editor import (
- GlossaryTableModel,
- GlossaryEditor,
- EntryEditDialog,
- CATEGORY_NAMES,
- )
- @pytest.fixture
- def app(qtbot):
- """Create QApplication for tests."""
- return QApplication.instance() or QApplication([])
- @pytest.fixture
- def sample_glossary():
- """Create a sample glossary for testing."""
- glossary = Glossary()
- glossary.add(GlossaryEntry("林风", "Lin Feng", TermCategory.CHARACTER, "Main protagonist"))
- glossary.add(GlossaryEntry("火球术", "Fireball", TermCategory.SKILL, "Basic fire spell"))
- glossary.add(GlossaryEntry("东方大陆", "Eastern Continent", TermCategory.LOCATION, ""))
- glossary.add(GlossaryEntry("龙剑", "Dragon Sword", TermCategory.ITEM, "Legendary weapon"))
- return glossary
- @pytest.fixture
- def temp_json_file():
- """Create a temporary JSON file for testing."""
- with tempfile.NamedTemporaryFile(mode="w", suffix=".json", delete=False) as f:
- data = [
- {
- "source": "测试术语",
- "target": "Test Term",
- "category": "other",
- "context": "Test context"
- }
- ]
- json.dump(data, f, ensure_ascii=False)
- return Path(f.name)
- class TestGlossaryTableModel:
- """Tests for GlossaryTableModel."""
- def test_initialization(self, app, sample_glossary):
- """Test model initialization."""
- model = GlossaryTableModel(sample_glossary)
- assert model.rowCount() == 4
- assert model.columnCount() == 4
- def test_data_retrieval(self, app, sample_glossary):
- """Test data retrieval from model."""
- model = GlossaryTableModel(sample_glossary)
- index = model.index(0, 0) # First row, source column
- assert model.data(index) == "林风"
- index = model.index(0, 1) # First row, target column
- assert model.data(index) == "Lin Feng"
- index = model.index(0, 2) # First row, category column
- assert model.data(index) == "人名"
- def test_category_filter(self, app, sample_glossary):
- """Test category filtering."""
- model = GlossaryTableModel(sample_glossary)
- # Filter by character
- model.set_category_filter(TermCategory.CHARACTER)
- assert model.rowCount() == 1
- assert model.filtered_count == 1
- assert model.entry_count == 4
- # Filter by skill
- model.set_category_filter(TermCategory.SKILL)
- assert model.rowCount() == 1
- # Clear filter
- model.set_category_filter(None)
- assert model.rowCount() == 4
- def test_search_filter(self, app, sample_glossary):
- """Test search filtering."""
- model = GlossaryTableModel(sample_glossary)
- # Search for "Lin"
- model.set_search_text("Lin")
- assert model.rowCount() == 2 # "Lin Feng" and "Dragon Sword"
- # Search for "火"
- model.set_search_text("火")
- assert model.rowCount() == 2 # "火球术" and "龙剑"
- # Clear search
- model.set_search_text("")
- assert model.rowCount() == 4
- def test_get_entry_at_row(self, app, sample_glossary):
- """Test getting entry at specific row."""
- model = GlossaryTableModel(sample_glossary)
- entry = model.get_entry_at_row(0)
- assert entry is not None
- assert entry.source == "林风"
- entry = model.get_entry_at_row(10)
- assert entry is None
- def test_refresh(self, app, sample_glossary):
- """Test model refresh."""
- model = GlossaryTableModel(sample_glossary)
- assert model.rowCount() == 4
- # Add new entry
- sample_glossary.add(GlossaryEntry("新术语", "New Term", TermCategory.OTHER))
- model.refresh()
- assert model.rowCount() == 5
- class TestGlossaryEntry:
- """Tests for GlossaryEntry validation."""
- def test_valid_entry(self):
- """Test creating a valid entry."""
- entry = GlossaryEntry("测试", "Test", TermCategory.OTHER)
- assert entry.source == "测试"
- assert entry.target == "Test"
- assert entry.category == TermCategory.OTHER
- def test_empty_source(self):
- """Test that empty source raises error."""
- with pytest.raises(ValueError):
- GlossaryEntry("", "Test", TermCategory.OTHER)
- def test_whitespace_only_source(self):
- """Test that whitespace-only source raises error."""
- with pytest.raises(ValueError):
- GlossaryEntry(" ", "Test", TermCategory.OTHER)
- def test_empty_target(self):
- """Test that empty target raises error."""
- with pytest.raises(ValueError):
- GlossaryEntry("测试", "", TermCategory.OTHER)
- def test_length_property(self):
- """Test the length property."""
- entry = GlossaryEntry("测试术语", "Test", TermCategory.OTHER)
- assert entry.length == 4
- @pytest.mark.skipif(not QApplication.instance(), reason="Requires QApplication")
- class TestGlossaryEditor:
- """Tests for GlossaryEditor widget."""
- def test_initialization(self, qtbot, sample_glossary):
- """Test editor initialization."""
- editor = GlossaryEditor(sample_glossary)
- qtbot.addWidget(editor)
- assert editor.glossary == sample_glossary
- # Editor should display all entries
- assert editor._model.entry_count == 4
- def test_add_entry(self, qtbot, sample_glossary):
- """Test adding a new entry."""
- editor = GlossaryEditor(sample_glossary)
- qtbot.addWidget(editor)
- initial_count = len(sample_glossary)
- # Add new entry
- new_entry = GlossaryEntry("魔法", "Magic", TermCategory.SKILL)
- sample_glossary.add(new_entry)
- editor.refresh_data()
- assert len(sample_glossary) == initial_count + 1
- def test_delete_entry(self, qtbot, sample_glossary):
- """Test deleting an entry."""
- editor = GlossaryEditor(sample_glossary)
- qtbot.addWidget(editor)
- initial_count = len(sample_glossary)
- # Remove entry
- sample_glossary.remove("林风")
- editor.refresh_data()
- assert len(sample_glossary) == initial_count - 1
- def test_set_glossary(self, qtbot, sample_glossary):
- """Test setting a new glossary."""
- editor = GlossaryEditor(sample_glossary)
- qtbot.addWidget(editor)
- new_glossary = Glossary()
- new_glossary.add(GlossaryEntry("测试", "Test", TermCategory.OTHER))
- editor.set_glossary(new_glossary)
- assert editor.glossary == new_glossary
- assert editor._model.entry_count == 1
- class TestGlossaryJson:
- """Tests for JSON import/export functionality."""
- def test_save_to_file(self, sample_glossary, tmp_path):
- """Test saving glossary to JSON file."""
- json_path = tmp_path / "test_glossary.json"
- sample_glossary.save_to_file(json_path)
- assert json_path.exists()
- # Verify content
- with open(json_path, "r", encoding="utf-8") as f:
- data = json.load(f)
- assert len(data) == 4
- assert data[0]["source"] == "林风"
- assert data[0]["target"] == "Lin Feng"
- assert data[0]["category"] == "character"
- def test_load_from_file(self, temp_json_file):
- """Test loading glossary from JSON file."""
- glossary = Glossary()
- glossary.load_from_file(temp_json_file)
- assert len(glossary) == 1
- entry = glossary.get("测试术语")
- assert entry is not None
- assert entry.target == "Test Term"
- assert entry.category == TermCategory.OTHER
- def test_load_nonexistent_file(self):
- """Test loading from non-existent file."""
- glossary = Glossary()
- with pytest.raises(FileNotFoundError):
- glossary.load_from_file(Path("/nonexistent/path.json"))
- def test_atomic_write(self, sample_glossary, tmp_path):
- """Test that save uses atomic write."""
- json_path = tmp_path / "test_glossary.json"
- # Save should be atomic
- sample_glossary.save_to_file(json_path)
- # File should exist and be complete
- assert json_path.exists()
- with open(json_path, "r", encoding="utf-8") as f:
- data = json.load(f)
- # Should have all entries
- assert len(data) == 4
- class TestCategoryNames:
- """Tests for category name mappings."""
- def test_all_categories_have_names(self):
- """Test that all categories have display names."""
- for category in TermCategory:
- assert category in CATEGORY_NAMES
- assert CATEGORY_NAMES[category]
- def test_chinese_names(self):
- """Test Chinese category names."""
- assert CATEGORY_NAMES[TermCategory.CHARACTER] == "人名"
- assert CATEGORY_NAMES[TermCategory.SKILL] == "技能"
- assert CATEGORY_NAMES[TermCategory.LOCATION] == "地名"
- assert CATEGORY_NAMES[TermCategory.ITEM] == "物品"
- assert CATEGORY_NAMES[TermCategory.ORGANIZATION] == "组织"
- assert CATEGORY_NAMES[TermCategory.OTHER] == "其他"
|