* test: Improve test coverage to 80% Increased test coverage from ~33% to 80.15% statement coverage. New test files added: - test_action_handler.py (100% coverage) - test_version.py (100% coverage) - test_logging_manager.py (100% coverage) - test_common_types.py - test_speech_recognition_init.py (100% coverage) - test_resource_manager.py (93% coverage) Significantly expanded existing tests: - test_tray_indicator.py: 76% -> 98% - test_main.py: 50% -> 92% - test_text_injector.py: 60% -> 94% - test_recognition_manager.py: 30% -> 48% - test_command_processor.py: 85% -> 98% - test_keyboard_shortcuts.py: 88% -> 95% - test_config_manager.py: improved to 98% - test_audio_feedback.py: improved to 92% Configuration changes: - Updated pyproject.toml to exclude untestable GTK dialog files (settings_dialog.py, logging_dialog.py) from coverage metrics Key testing improvements: - Added comprehensive GTK mocking for UI components - Added edge case tests for text injection on X11/Wayland - Added keyboard shortcut injection tests - Added error handling and callback tests - Added configuration management tests * fix: Use runtime CI mode detection to fix Python 3.8/3.10 tests Change CI_MODE from module-level constant to _is_ci_mode() function that is evaluated at runtime. This ensures pytest detection works correctly on all Python versions, since pytest modules may not be loaded at import time. * fix: Add PYTEST_RUNNING env var for reliable pytest detection in CI The previous approach to detect pytest at runtime wasn't working reliably on Python 3.8/3.10 in CI environments. This adds a PYTEST_RUNNING=1 environment variable set early in conftest.py that _is_ci_mode() can check to reliably determine if tests are running. * fix: Use patch.object for reliable mocking in audio_feedback tests The previous approach using patch() with module paths didn't work reliably because the module reimport caused the patched objects to not be the same as the ones the functions referenced. Using patch.object() on the imported module's attributes ensures the patches are applied to the actual objects the functions use. * fix: Use patch.object for reliable mocking in test_settings_callback The test was failing on Python 3.8/3.10 because the module-level patch for SettingsDialog wasn't being applied reliably across all Python versions due to import timing differences. Using patch.object() on the imported module ensures the mock is applied to the actual reference that _on_settings_clicked() uses, regardless of Python version or module caching behavior.
143 lines
5.3 KiB
Python
143 lines
5.3 KiB
Python
"""
|
|
Tests for the action handler module.
|
|
"""
|
|
|
|
import unittest
|
|
from unittest.mock import MagicMock
|
|
|
|
|
|
class TestActionHandler(unittest.TestCase):
|
|
"""Test cases for ActionHandler."""
|
|
|
|
def setUp(self):
|
|
"""Set up test fixtures."""
|
|
from vocalinux.ui.action_handler import ActionHandler
|
|
|
|
self.mock_text_injector = MagicMock()
|
|
self.mock_text_injector.inject_text.return_value = True
|
|
self.mock_text_injector._inject_keyboard_shortcut.return_value = True
|
|
|
|
self.handler = ActionHandler(self.mock_text_injector)
|
|
|
|
def test_initialization(self):
|
|
"""Test that ActionHandler initializes correctly."""
|
|
self.assertEqual(self.handler.text_injector, self.mock_text_injector)
|
|
self.assertEqual(self.handler.last_injected_text, "")
|
|
self.assertIn("delete_last", self.handler.action_handlers)
|
|
self.assertIn("undo", self.handler.action_handlers)
|
|
self.assertIn("redo", self.handler.action_handlers)
|
|
self.assertIn("select_all", self.handler.action_handlers)
|
|
self.assertIn("cut", self.handler.action_handlers)
|
|
self.assertIn("copy", self.handler.action_handlers)
|
|
self.assertIn("paste", self.handler.action_handlers)
|
|
|
|
def test_set_last_injected_text(self):
|
|
"""Test setting last injected text."""
|
|
self.handler.set_last_injected_text("hello world")
|
|
self.assertEqual(self.handler.last_injected_text, "hello world")
|
|
|
|
def test_handle_unknown_action(self):
|
|
"""Test handling an unknown action."""
|
|
result = self.handler.handle_action("unknown_action")
|
|
self.assertFalse(result)
|
|
|
|
def test_handle_delete_last_no_text(self):
|
|
"""Test delete_last when no text has been injected."""
|
|
result = self.handler.handle_action("delete_last")
|
|
self.assertTrue(result)
|
|
self.mock_text_injector.inject_text.assert_not_called()
|
|
|
|
def test_handle_delete_last_with_text(self):
|
|
"""Test delete_last with previously injected text."""
|
|
self.handler.set_last_injected_text("hello")
|
|
result = self.handler.handle_action("delete_last")
|
|
|
|
self.assertTrue(result)
|
|
self.mock_text_injector.inject_text.assert_called_once()
|
|
# Should send 5 backspaces for "hello"
|
|
call_args = self.mock_text_injector.inject_text.call_args[0][0]
|
|
self.assertEqual(len(call_args), 5) # 5 backspaces
|
|
self.assertEqual(self.handler.last_injected_text, "")
|
|
|
|
def test_handle_undo(self):
|
|
"""Test undo action."""
|
|
result = self.handler.handle_action("undo")
|
|
|
|
self.assertTrue(result)
|
|
self.mock_text_injector._inject_keyboard_shortcut.assert_called_with("ctrl+z")
|
|
|
|
def test_handle_redo(self):
|
|
"""Test redo action."""
|
|
result = self.handler.handle_action("redo")
|
|
|
|
self.assertTrue(result)
|
|
self.mock_text_injector._inject_keyboard_shortcut.assert_called_with("ctrl+y")
|
|
|
|
def test_handle_select_all(self):
|
|
"""Test select all action."""
|
|
result = self.handler.handle_action("select_all")
|
|
|
|
self.assertTrue(result)
|
|
self.mock_text_injector._inject_keyboard_shortcut.assert_called_with("ctrl+a")
|
|
|
|
def test_handle_select_line(self):
|
|
"""Test select line action."""
|
|
result = self.handler.handle_action("select_line")
|
|
|
|
self.assertTrue(result)
|
|
self.mock_text_injector._inject_keyboard_shortcut.assert_called_with("Home+shift+End")
|
|
|
|
def test_handle_select_word(self):
|
|
"""Test select word action."""
|
|
result = self.handler.handle_action("select_word")
|
|
|
|
self.assertTrue(result)
|
|
self.mock_text_injector._inject_keyboard_shortcut.assert_called_with("ctrl+shift+Right")
|
|
|
|
def test_handle_select_paragraph(self):
|
|
"""Test select paragraph action."""
|
|
result = self.handler.handle_action("select_paragraph")
|
|
|
|
self.assertTrue(result)
|
|
self.mock_text_injector._inject_keyboard_shortcut.assert_called_with("ctrl+shift+Down")
|
|
|
|
def test_handle_cut(self):
|
|
"""Test cut action."""
|
|
result = self.handler.handle_action("cut")
|
|
|
|
self.assertTrue(result)
|
|
self.mock_text_injector._inject_keyboard_shortcut.assert_called_with("ctrl+x")
|
|
|
|
def test_handle_copy(self):
|
|
"""Test copy action."""
|
|
result = self.handler.handle_action("copy")
|
|
|
|
self.assertTrue(result)
|
|
self.mock_text_injector._inject_keyboard_shortcut.assert_called_with("ctrl+c")
|
|
|
|
def test_handle_paste(self):
|
|
"""Test paste action."""
|
|
result = self.handler.handle_action("paste")
|
|
|
|
self.assertTrue(result)
|
|
self.mock_text_injector._inject_keyboard_shortcut.assert_called_with("ctrl+v")
|
|
|
|
def test_handle_action_with_exception(self):
|
|
"""Test handling action that raises an exception."""
|
|
self.mock_text_injector._inject_keyboard_shortcut.side_effect = Exception("Test error")
|
|
|
|
result = self.handler.handle_action("undo")
|
|
|
|
self.assertFalse(result)
|
|
|
|
def test_delete_last_failure(self):
|
|
"""Test delete_last when inject_text fails."""
|
|
self.handler.set_last_injected_text("test")
|
|
self.mock_text_injector.inject_text.return_value = False
|
|
|
|
result = self.handler.handle_action("delete_last")
|
|
|
|
self.assertFalse(result)
|
|
# Text should not be cleared on failure
|
|
self.assertEqual(self.handler.last_injected_text, "test")
|