Import the vocalinux source tree (tray app, Next.js site in web/, packaging, installers, docs and tests) on top of the Gitea template commit. The project README replaces the template README, and the template CI files (.gitea/workflows/ci.yml, .jscpd.json, CI.md) are kept.
368 lines
14 KiB
Python
368 lines
14 KiB
Python
"""
|
|
Tests for keyboard backend base class and helper functions.
|
|
|
|
This module tests the base.py module including:
|
|
- parse_shortcut() function and error cases
|
|
- get_shortcut_display_name() function
|
|
- KeyboardBackend abstract class
|
|
"""
|
|
|
|
from unittest.mock import MagicMock
|
|
|
|
import pytest
|
|
|
|
from vocalinux.ui.keyboard_backends.base import (
|
|
DEFAULT_SHORTCUT,
|
|
DEFAULT_SHORTCUT_MODE,
|
|
SHORTCUT_DISPLAY_NAMES,
|
|
SHORTCUT_MODE_DISPLAY_NAMES,
|
|
SHORTCUT_MODES,
|
|
SUPPORTED_SHORTCUTS,
|
|
KeyboardBackend,
|
|
get_shortcut_display_name,
|
|
parse_shortcut,
|
|
)
|
|
|
|
|
|
class TestParseShortcut:
|
|
"""Test the parse_shortcut() function."""
|
|
|
|
def test_parse_shortcut_ctrl_either_side(self):
|
|
"""Test parsing ctrl+ctrl (either side)."""
|
|
result = parse_shortcut("ctrl+ctrl")
|
|
assert result == "ctrl"
|
|
|
|
def test_parse_shortcut_alt_either_side(self):
|
|
"""Test parsing alt+alt (either side)."""
|
|
result = parse_shortcut("alt+alt")
|
|
assert result == "alt"
|
|
|
|
def test_parse_shortcut_shift_either_side(self):
|
|
"""Test parsing shift+shift (either side)."""
|
|
result = parse_shortcut("shift+shift")
|
|
assert result == "shift"
|
|
|
|
def test_parse_shortcut_left_ctrl(self):
|
|
"""Test parsing left_ctrl+left_ctrl."""
|
|
result = parse_shortcut("left_ctrl+left_ctrl")
|
|
assert result == "left_ctrl"
|
|
|
|
def test_parse_shortcut_right_alt(self):
|
|
"""Test parsing right_alt+right_alt."""
|
|
result = parse_shortcut("right_alt+right_alt")
|
|
assert result == "right_alt"
|
|
|
|
def test_parse_shortcut_left_shift(self):
|
|
"""Test parsing left_shift+left_shift."""
|
|
result = parse_shortcut("left_shift+left_shift")
|
|
assert result == "left_shift"
|
|
|
|
def test_parse_shortcut_case_insensitive(self):
|
|
"""Test that parse_shortcut is case insensitive."""
|
|
result = parse_shortcut("CTRL+CTRL")
|
|
assert result == "ctrl"
|
|
|
|
def test_parse_shortcut_with_whitespace(self):
|
|
"""Test that parse_shortcut handles whitespace."""
|
|
result = parse_shortcut(" ctrl+ctrl ")
|
|
assert result == "ctrl"
|
|
|
|
def test_parse_shortcut_invalid_shortcut(self):
|
|
"""Test that parse_shortcut raises ValueError for invalid shortcut."""
|
|
with pytest.raises(ValueError) as excinfo:
|
|
parse_shortcut("invalid_shortcut")
|
|
assert "Unsupported shortcut" in str(excinfo.value)
|
|
assert "invalid_shortcut" in str(excinfo.value)
|
|
|
|
def test_parse_shortcut_empty_string(self):
|
|
"""Test that parse_shortcut raises ValueError for empty string."""
|
|
with pytest.raises(ValueError):
|
|
parse_shortcut("")
|
|
|
|
def test_parse_shortcut_partial_match(self):
|
|
"""Test that parse_shortcut doesn't accept partial matches."""
|
|
with pytest.raises(ValueError):
|
|
parse_shortcut("ctrl")
|
|
|
|
def test_parse_shortcut_all_supported(self):
|
|
"""Test that all supported shortcuts can be parsed."""
|
|
for shortcut_key in SUPPORTED_SHORTCUTS.keys():
|
|
result = parse_shortcut(shortcut_key)
|
|
assert result == SUPPORTED_SHORTCUTS[shortcut_key]
|
|
|
|
|
|
class TestGetShortcutDisplayName:
|
|
"""Test the get_shortcut_display_name() function."""
|
|
|
|
def test_get_display_name_ctrl_no_mode(self):
|
|
"""Test getting display name for ctrl without mode."""
|
|
result = get_shortcut_display_name("ctrl+ctrl")
|
|
assert result == "Ctrl (either side)"
|
|
|
|
def test_get_display_name_alt_no_mode(self):
|
|
"""Test getting display name for alt without mode."""
|
|
result = get_shortcut_display_name("alt+alt")
|
|
assert result == "Alt (either side)"
|
|
|
|
def test_get_display_name_shift_no_mode(self):
|
|
"""Test getting display name for shift without mode."""
|
|
result = get_shortcut_display_name("shift+shift")
|
|
assert result == "Shift (either side)"
|
|
|
|
def test_get_display_name_left_ctrl_no_mode(self):
|
|
"""Test getting display name for left_ctrl without mode."""
|
|
result = get_shortcut_display_name("left_ctrl+left_ctrl")
|
|
assert result == "Left Ctrl"
|
|
|
|
def test_get_display_name_right_alt_no_mode(self):
|
|
"""Test getting display name for right_alt without mode."""
|
|
result = get_shortcut_display_name("right_alt+right_alt")
|
|
assert result == "Right Alt"
|
|
|
|
def test_get_display_name_ctrl_toggle_mode(self):
|
|
"""Test getting display name for ctrl with toggle mode."""
|
|
result = get_shortcut_display_name("ctrl+ctrl", mode="toggle")
|
|
assert result == "Double-tap Ctrl"
|
|
|
|
def test_get_display_name_ctrl_push_to_talk_mode(self):
|
|
"""Test getting display name for ctrl with push_to_talk mode."""
|
|
result = get_shortcut_display_name("ctrl+ctrl", mode="push_to_talk")
|
|
assert result == "Hold Ctrl"
|
|
|
|
def test_get_display_name_alt_toggle_mode(self):
|
|
"""Test getting display name for alt with toggle mode."""
|
|
result = get_shortcut_display_name("alt+alt", mode="toggle")
|
|
assert result == "Double-tap Alt"
|
|
|
|
def test_get_display_name_left_ctrl_toggle(self):
|
|
"""Test getting display name for left_ctrl with toggle mode."""
|
|
result = get_shortcut_display_name("left_ctrl+left_ctrl", mode="toggle")
|
|
assert result == "Double-tap Left Ctrl"
|
|
|
|
def test_get_display_name_left_ctrl_push_to_talk(self):
|
|
"""Test getting display name for left_ctrl with push_to_talk mode."""
|
|
result = get_shortcut_display_name("left_ctrl+left_ctrl", mode="push_to_talk")
|
|
assert result == "Hold Left Ctrl"
|
|
|
|
def test_get_display_name_unknown_shortcut(self):
|
|
"""Test getting display name for unknown shortcut (returns the shortcut itself)."""
|
|
result = get_shortcut_display_name("unknown+unknown")
|
|
assert result == "unknown+unknown"
|
|
|
|
def test_get_display_name_unknown_mode(self):
|
|
"""Test getting display name with unknown mode (falls back to base name)."""
|
|
result = get_shortcut_display_name("ctrl+ctrl", mode="unknown_mode")
|
|
assert result == "Ctrl (either side)"
|
|
|
|
def test_get_display_name_all_supported_shortcuts_no_mode(self):
|
|
"""Test that all supported shortcuts have display names."""
|
|
for shortcut_key in SUPPORTED_SHORTCUTS.keys():
|
|
result = get_shortcut_display_name(shortcut_key)
|
|
assert result # Should not be empty
|
|
assert isinstance(result, str)
|
|
|
|
def test_get_display_name_all_shortcuts_with_toggle_mode(self):
|
|
"""Test all shortcuts with toggle mode."""
|
|
for shortcut_key in SUPPORTED_SHORTCUTS.keys():
|
|
result = get_shortcut_display_name(shortcut_key, mode="toggle")
|
|
assert result # Should not be empty
|
|
assert isinstance(result, str)
|
|
|
|
def test_get_display_name_all_shortcuts_with_push_to_talk_mode(self):
|
|
"""Test all shortcuts with push_to_talk mode."""
|
|
for shortcut_key in SUPPORTED_SHORTCUTS.keys():
|
|
result = get_shortcut_display_name(shortcut_key, mode="push_to_talk")
|
|
assert result # Should not be empty
|
|
assert isinstance(result, str)
|
|
|
|
|
|
class ConcreteKeyboardBackend(KeyboardBackend):
|
|
"""Concrete implementation of KeyboardBackend for testing."""
|
|
|
|
def start(self) -> bool:
|
|
"""Start the backend."""
|
|
return True
|
|
|
|
def stop(self) -> None:
|
|
"""Stop the backend."""
|
|
pass
|
|
|
|
def is_available(self) -> bool:
|
|
"""Check if available."""
|
|
return True
|
|
|
|
def get_permission_hint(self):
|
|
"""Get permission hint."""
|
|
return None
|
|
|
|
|
|
class TestKeyboardBackend:
|
|
"""Test the KeyboardBackend abstract base class."""
|
|
|
|
def test_init_default_values(self):
|
|
"""Test backend initialization with default values."""
|
|
backend = ConcreteKeyboardBackend()
|
|
assert backend.shortcut == DEFAULT_SHORTCUT
|
|
assert backend.mode == DEFAULT_SHORTCUT_MODE
|
|
assert backend.active is False
|
|
assert backend.double_tap_callback is None
|
|
assert backend.key_press_callback is None
|
|
assert backend.key_release_callback is None
|
|
|
|
def test_init_custom_shortcut_and_mode(self):
|
|
"""Test backend initialization with custom shortcut and mode."""
|
|
backend = ConcreteKeyboardBackend(shortcut="alt+alt", mode="push_to_talk")
|
|
assert backend.shortcut == "alt+alt"
|
|
assert backend.mode == "push_to_talk"
|
|
|
|
def test_shortcut_property(self):
|
|
"""Test shortcut property is read-only."""
|
|
backend = ConcreteKeyboardBackend(shortcut="ctrl+ctrl")
|
|
assert backend.shortcut == "ctrl+ctrl"
|
|
|
|
def test_mode_property(self):
|
|
"""Test mode property is read-only."""
|
|
backend = ConcreteKeyboardBackend(mode="toggle")
|
|
assert backend.mode == "toggle"
|
|
|
|
def test_modifier_key_property(self):
|
|
"""Test modifier_key property."""
|
|
backend = ConcreteKeyboardBackend(shortcut="alt+alt")
|
|
assert backend.modifier_key == "alt"
|
|
|
|
def test_modifier_key_left_ctrl(self):
|
|
"""Test modifier_key for left_ctrl shortcut."""
|
|
backend = ConcreteKeyboardBackend(shortcut="left_ctrl+left_ctrl")
|
|
assert backend.modifier_key == "left_ctrl"
|
|
|
|
def test_set_mode_valid(self):
|
|
"""Test setting a valid mode."""
|
|
backend = ConcreteKeyboardBackend()
|
|
backend.set_mode("push_to_talk")
|
|
assert backend.mode == "push_to_talk"
|
|
|
|
def test_set_mode_toggle(self):
|
|
"""Test setting mode to toggle."""
|
|
backend = ConcreteKeyboardBackend(mode="push_to_talk")
|
|
backend.set_mode("toggle")
|
|
assert backend.mode == "toggle"
|
|
|
|
def test_set_mode_invalid(self):
|
|
"""Test setting an invalid mode raises ValueError."""
|
|
backend = ConcreteKeyboardBackend()
|
|
with pytest.raises(ValueError) as excinfo:
|
|
backend.set_mode("invalid_mode")
|
|
assert "Invalid mode" in str(excinfo.value)
|
|
|
|
def test_set_shortcut_ctrl(self):
|
|
"""Test setting shortcut to ctrl+ctrl."""
|
|
backend = ConcreteKeyboardBackend(shortcut="alt+alt")
|
|
backend.set_shortcut("ctrl+ctrl")
|
|
assert backend.shortcut == "ctrl+ctrl"
|
|
assert backend.modifier_key == "ctrl"
|
|
|
|
def test_set_shortcut_left_shift(self):
|
|
"""Test setting shortcut to left_shift+left_shift."""
|
|
backend = ConcreteKeyboardBackend()
|
|
backend.set_shortcut("left_shift+left_shift")
|
|
assert backend.shortcut == "left_shift+left_shift"
|
|
assert backend.modifier_key == "left_shift"
|
|
|
|
def test_set_shortcut_invalid(self):
|
|
"""Test setting an invalid shortcut raises ValueError."""
|
|
backend = ConcreteKeyboardBackend()
|
|
with pytest.raises(ValueError):
|
|
backend.set_shortcut("invalid+invalid")
|
|
|
|
def test_register_toggle_callback(self):
|
|
"""Test registering a toggle callback."""
|
|
backend = ConcreteKeyboardBackend()
|
|
callback = MagicMock()
|
|
backend.register_toggle_callback(callback)
|
|
assert backend.double_tap_callback == callback
|
|
|
|
def test_register_toggle_callback_none(self):
|
|
"""Test unregistering toggle callback by passing None."""
|
|
backend = ConcreteKeyboardBackend()
|
|
callback = MagicMock()
|
|
backend.register_toggle_callback(callback)
|
|
backend.register_toggle_callback(None)
|
|
assert backend.double_tap_callback is None
|
|
|
|
def test_register_press_callback(self):
|
|
"""Test registering a press callback."""
|
|
backend = ConcreteKeyboardBackend()
|
|
callback = MagicMock()
|
|
backend.register_press_callback(callback)
|
|
assert backend.key_press_callback == callback
|
|
|
|
def test_register_press_callback_none(self):
|
|
"""Test unregistering press callback by passing None."""
|
|
backend = ConcreteKeyboardBackend()
|
|
callback = MagicMock()
|
|
backend.register_press_callback(callback)
|
|
backend.register_press_callback(None)
|
|
assert backend.key_press_callback is None
|
|
|
|
def test_register_release_callback(self):
|
|
"""Test registering a release callback."""
|
|
backend = ConcreteKeyboardBackend()
|
|
callback = MagicMock()
|
|
backend.register_release_callback(callback)
|
|
assert backend.key_release_callback == callback
|
|
|
|
def test_register_release_callback_none(self):
|
|
"""Test unregistering release callback by passing None."""
|
|
backend = ConcreteKeyboardBackend()
|
|
callback = MagicMock()
|
|
backend.register_release_callback(callback)
|
|
backend.register_release_callback(None)
|
|
assert backend.key_release_callback is None
|
|
|
|
def test_multiple_callbacks_independent(self):
|
|
"""Test that multiple callbacks can be registered independently."""
|
|
backend = ConcreteKeyboardBackend()
|
|
toggle_cb = MagicMock()
|
|
press_cb = MagicMock()
|
|
release_cb = MagicMock()
|
|
|
|
backend.register_toggle_callback(toggle_cb)
|
|
backend.register_press_callback(press_cb)
|
|
backend.register_release_callback(release_cb)
|
|
|
|
assert backend.double_tap_callback == toggle_cb
|
|
assert backend.key_press_callback == press_cb
|
|
assert backend.key_release_callback == release_cb
|
|
|
|
def test_start_method(self):
|
|
"""Test that start() method can be called."""
|
|
backend = ConcreteKeyboardBackend()
|
|
result = backend.start()
|
|
assert result is True
|
|
|
|
def test_stop_method(self):
|
|
"""Test that stop() method can be called."""
|
|
backend = ConcreteKeyboardBackend()
|
|
backend.stop() # Should not raise
|
|
|
|
def test_is_available_method(self):
|
|
"""Test that is_available() method returns bool."""
|
|
backend = ConcreteKeyboardBackend()
|
|
result = backend.is_available()
|
|
assert isinstance(result, bool)
|
|
|
|
def test_get_permission_hint_method(self):
|
|
"""Test that get_permission_hint() method returns Optional[str]."""
|
|
backend = ConcreteKeyboardBackend()
|
|
result = backend.get_permission_hint()
|
|
assert result is None or isinstance(result, str)
|
|
|
|
def test_active_flag_management(self):
|
|
"""Test that active flag is properly managed."""
|
|
backend = ConcreteKeyboardBackend()
|
|
assert backend.active is False
|
|
backend.active = True
|
|
assert backend.active is True
|
|
backend.active = False
|
|
assert backend.active is False
|