""" Tests for the Keyboard Shortcuts UI component (Story 7.13). """ import pytest from pathlib import Path from PyQt6.QtWidgets import QApplication from PyQt6.QtGui import QKeySequence from src.ui.shortcuts import ( StandardAction, DEFAULT_SHORTCUTS, ACTION_NAMES, ShortcutEntry, ShortcutsManager, ShortcutsModel, get_shortcuts_manager, ) @pytest.fixture def app(qtbot): """Create QApplication for tests.""" return QApplication.instance() or QApplication([]) @pytest.fixture def clean_manager(): """Create a fresh ShortcutsManager for testing.""" import src.ui.shortcuts as sc sc._shortcuts_manager_instance = None manager = ShortcutsManager() return manager class TestStandardAction: """Tests for StandardAction enum.""" def test_action_values(self): """Test StandardAction enum values.""" assert StandardAction.OPEN_FILE.value == "open_file" assert StandardAction.SAVE_FILE.value == "save_file" assert StandardAction.START_TRANSLATION.value == "start_translation" class TestDefaultShortcuts: """Tests for default shortcuts.""" def test_all_actions_have_defaults(self): """Test that all standard actions have default shortcuts.""" for action in StandardAction: assert action in DEFAULT_SHORTCUTS assert DEFAULT_SHORTCUTS[action] def test_all_actions_have_names(self): """Test that all standard actions have display names.""" for action in StandardAction: assert action in ACTION_NAMES assert ACTION_NAMES[action] def test_common_shortcuts(self): """Test common shortcut defaults.""" assert DEFAULT_SHORTCUTS[StandardAction.OPEN_FILE] == "Ctrl+O" assert DEFAULT_SHORTCUTS[StandardAction.SAVE_FILE] == "Ctrl+S" assert DEFAULT_SHORTCUTS[StandardAction.START_TRANSLATION] == "F5" assert DEFAULT_SHORTCUTS[StandardAction.ABOUT] == "Ctrl+F1" class TestShortcutEntry: """Tests for ShortcutEntry dataclass.""" def test_entry_creation(self): """Test creating a shortcut entry.""" entry = ShortcutEntry( action=StandardAction.OPEN_FILE, key_sequence="Ctrl+O", default_key_sequence="Ctrl+O", name=ACTION_NAMES[StandardAction.OPEN_FILE], category="文件" ) assert entry.action == StandardAction.OPEN_FILE assert entry.key_sequence == "Ctrl+O" assert entry.default_key_sequence == "Ctrl+O" def test_is_modified_false(self): """Test is_modified when not modified.""" entry = ShortcutEntry( action=StandardAction.OPEN_FILE, key_sequence="Ctrl+O", default_key_sequence="Ctrl+O", name="Open File", category="File" ) assert entry.is_modified is False def test_is_modified_true(self): """Test is_modified when modified.""" entry = ShortcutEntry( action=StandardAction.OPEN_FILE, key_sequence="Ctrl+Shift+O", default_key_sequence="Ctrl+O", name="Open File", category="File" ) assert entry.is_modified is True class TestShortcutsManager: """Tests for ShortcutsManager.""" def test_initialization(self, clean_manager): """Test manager initialization.""" assert isinstance(clean_manager, ShortcutsManager) def test_get_shortcut(self, clean_manager): """Test getting shortcut for an action.""" shortcut = clean_manager.get_shortcut(StandardAction.OPEN_FILE) assert isinstance(shortcut, QKeySequence) assert shortcut.toString() == "Ctrl+O" def test_get_all_shortcuts(self, clean_manager): """Test all actions have shortcuts.""" for action in StandardAction: shortcut = clean_manager.get_shortcut(action) assert isinstance(shortcut, QKeySequence) def test_set_shortcut(self, clean_manager): """Test setting a shortcut.""" result = clean_manager.set_shortcut(StandardAction.OPEN_FILE, "Ctrl+Shift+O") assert result is True shortcut = clean_manager.get_shortcut(StandardAction.OPEN_FILE) assert shortcut.toString() == "Ctrl+Shift+O" def test_set_shortcut_conflict(self, clean_manager): """Test that conflicting shortcuts are rejected.""" # Try to set OPEN_FILE to the same as SAVE_FILE result = clean_manager.set_shortcut(StandardAction.OPEN_FILE, "Ctrl+S") assert result is False def test_reset_action(self, clean_manager): """Test resetting a single action to default.""" # First change it clean_manager.set_shortcut(StandardAction.OPEN_FILE, "Ctrl+Shift+O") # Reset to default clean_manager.reset_action(StandardAction.OPEN_FILE) shortcut = clean_manager.get_shortcut(StandardAction.OPEN_FILE) assert shortcut.toString() == DEFAULT_SHORTCUTS[StandardAction.OPEN_FILE] def test_reset_to_defaults(self, clean_manager): """Test resetting all shortcuts to defaults.""" # Change some shortcuts clean_manager.set_shortcut(StandardAction.OPEN_FILE, "Ctrl+Shift+O") clean_manager.set_shortcut(StandardAction.SAVE_FILE, "Ctrl+Shift+S") # Reset all clean_manager.reset_to_defaults() assert clean_manager.get_shortcut(StandardAction.OPEN_FILE).toString() == "Ctrl+O" assert clean_manager.get_shortcut(StandardAction.SAVE_FILE).toString() == "Ctrl+S" def test_get_all_entries(self, clean_manager): """Test getting all shortcut entries.""" entries = clean_manager.get_all_entries() assert len(entries) == len(StandardAction) for entry in entries: assert isinstance(entry, ShortcutEntry) assert isinstance(entry.action, StandardAction) assert entry.name assert entry.category assert entry.key_sequence assert entry.default_key_sequence def test_create_action(self, app, clean_manager): """Test creating a QAction with shortcut.""" callback_called = [] def callback(): callback_called.append(True) action = clean_manager.create_action( StandardAction.OPEN_FILE, parent=None, callback=callback ) assert action is not None assert action.text() == ACTION_NAMES[StandardAction.OPEN_FILE] assert action.shortcut() == clean_manager.get_shortcut(StandardAction.OPEN_FILE) def test_register_action(self, app, clean_manager): """Test registering an existing QAction.""" from PyQt6.QtWidgets import QAction qaction = QAction("Test", None) clean_manager.register_action(StandardAction.OPEN_FILE, qaction) assert qaction.shortcut() == clean_manager.get_shortcut(StandardAction.OPEN_FILE) def test_register_then_change_shortcut(self, app, clean_manager): """Test that registered QAction updates when shortcut changes.""" from PyQt6.QtWidgets import QAction qaction = QAction("Test", None) clean_manager.register_action(StandardAction.OPEN_FILE, qaction) # Change the shortcut clean_manager.set_shortcut(StandardAction.OPEN_FILE, "Ctrl+Shift+O") # QAction should be updated assert qaction.shortcut().toString() == "Ctrl+Shift+O" class TestShortcutsModel: """Tests for ShortcutsModel.""" def test_model_initialization(self, app, clean_manager): """Test model initialization.""" entries = clean_manager.get_all_entries() model = ShortcutsModel(entries) assert model.rowCount() == len(entries) assert model.columnCount() == 3 def test_data_retrieval(self, app, clean_manager): """Test data retrieval from model.""" entries = clean_manager.get_all_entries() model = ShortcutsModel(entries) index = model.index(0, 0) # First row, action column data = model.data(index) assert data # Should have a name index = model.index(0, 1) # First row, shortcut column data = model.data(index) assert isinstance(data, str) def test_header_data(self, app, clean_manager): """Test header data.""" entries = clean_manager.get_all_entries() model = ShortcutsModel(entries) assert model.headerData(0, Qt.Orientation.Horizontal) == "操作" assert model.headerData(1, Qt.Orientation.Horizontal) == "快捷键" assert model.headerData(2, Qt.Orientation.Horizontal) == "默认" def test_get_entry_at_row(self, app, clean_manager): """Test getting entry at specific row.""" entries = clean_manager.get_all_entries() model = ShortcutsModel(entries) entry = model.get_entry_at_row(0) assert entry is not None assert isinstance(entry, ShortcutEntry) entry = model.get_entry_at_row(999) assert entry is None class TestShortcutsSingleton: """Tests for the singleton pattern.""" def test_get_singleton(self): """Test getting singleton instance.""" manager = get_shortcuts_manager() assert manager is not None assert isinstance(manager, ShortcutsManager) def test_singleton_persistence(self): """Test that singleton returns same instance.""" manager1 = get_shortcuts_manager() manager2 = get_shortcuts_manager() assert manager1 is manager2