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.
659 lines
30 KiB
Python
659 lines
30 KiB
Python
"""
|
|
Tests for keyboard backends __init__.py module.
|
|
|
|
This module tests:
|
|
- DesktopEnvironment.detect() for all cases (wayland, x11, unknown, fallback env vars)
|
|
- create_backend() for all branches (preferred evdev, preferred pynput, unknown preferred,
|
|
wayland auto, x11 auto, no backends)
|
|
- Mocking EVDEV_AVAILABLE, PYNPUT_AVAILABLE, and backend classes
|
|
"""
|
|
|
|
import os
|
|
import sys
|
|
from unittest.mock import MagicMock, Mock, patch
|
|
|
|
import pytest
|
|
|
|
# Import the module under test
|
|
import vocalinux.ui.keyboard_backends as kb_module
|
|
from vocalinux.ui.keyboard_backends import (
|
|
EVDEV_AVAILABLE,
|
|
PYNPUT_AVAILABLE,
|
|
DesktopEnvironment,
|
|
_is_native_x11_session,
|
|
create_backend,
|
|
)
|
|
from vocalinux.ui.keyboard_backends.evdev_backend import EVDEV_AVAILABLE as EVDEV_BACKEND_AVAILABLE
|
|
from vocalinux.ui.keyboard_backends.pynput_backend import (
|
|
PYNPUT_AVAILABLE as PYNPUT_BACKEND_AVAILABLE,
|
|
)
|
|
|
|
|
|
class TestDesktopEnvironmentDetect:
|
|
"""Test DesktopEnvironment.detect() method."""
|
|
|
|
def test_package_availability_flags_match_backend_modules(self):
|
|
"""Test package-level availability flags mirror backend import checks."""
|
|
assert EVDEV_AVAILABLE == EVDEV_BACKEND_AVAILABLE
|
|
assert PYNPUT_AVAILABLE == PYNPUT_BACKEND_AVAILABLE
|
|
|
|
def test_detect_xdg_session_type_wayland(self):
|
|
"""Test detection of Wayland via XDG_SESSION_TYPE."""
|
|
with patch.dict(os.environ, {"XDG_SESSION_TYPE": "wayland"}, clear=False):
|
|
result = DesktopEnvironment.detect()
|
|
assert result == DesktopEnvironment.WAYLAND
|
|
|
|
def test_detect_xdg_session_type_x11(self):
|
|
"""Test detection of X11 via XDG_SESSION_TYPE."""
|
|
with patch.dict(os.environ, {"XDG_SESSION_TYPE": "x11"}, clear=False):
|
|
result = DesktopEnvironment.detect()
|
|
assert result == DesktopEnvironment.X11
|
|
|
|
def test_detect_flatpak_wayland_without_socket_uses_x11(self):
|
|
"""Flatpak with only X11/XWayland is labeled X11 (injection path).
|
|
|
|
Global shortcuts still prefer evdev via create_backend when FLATPAK_ID
|
|
is set — pynput over XWayland is not truly global.
|
|
"""
|
|
env = {
|
|
"FLATPAK_ID": "com.vocalinux.Vocalinux",
|
|
"XDG_SESSION_TYPE": "wayland",
|
|
"DISPLAY": ":0",
|
|
}
|
|
with patch.dict(os.environ, env, clear=True):
|
|
assert DesktopEnvironment.detect() == DesktopEnvironment.X11
|
|
|
|
def test_detect_flatpak_wayland_with_socket_stays_wayland(self):
|
|
"""With a Wayland socket exposed, detection still reports Wayland."""
|
|
env = {
|
|
"FLATPAK_ID": "com.vocalinux.Vocalinux",
|
|
"XDG_SESSION_TYPE": "wayland",
|
|
"WAYLAND_DISPLAY": "wayland-0",
|
|
}
|
|
with patch.dict(os.environ, env, clear=True):
|
|
assert DesktopEnvironment.detect() == DesktopEnvironment.WAYLAND
|
|
|
|
def test_create_backend_flatpak_prefers_evdev(self):
|
|
"""Flatpak must use evdev so hotkeys work when another app is focused."""
|
|
from vocalinux.ui.keyboard_backends import create_backend
|
|
|
|
fake = MagicMock()
|
|
fake.is_available.return_value = True
|
|
|
|
env = {
|
|
"FLATPAK_ID": "com.vocalinux.Vocalinux",
|
|
"XDG_SESSION_TYPE": "wayland",
|
|
"DISPLAY": ":0",
|
|
}
|
|
with patch.dict(os.environ, env, clear=True):
|
|
with (
|
|
patch.object(kb_module, "EVDEV_AVAILABLE", True),
|
|
patch.object(kb_module, "EvdevKeyboardBackend", return_value=fake) as mock_evdev,
|
|
patch.object(kb_module, "PYNPUT_AVAILABLE", True),
|
|
patch.object(kb_module, "PynputKeyboardBackend") as mock_pynput,
|
|
):
|
|
backend = create_backend()
|
|
assert backend is fake
|
|
mock_evdev.assert_called_once()
|
|
mock_pynput.assert_not_called()
|
|
|
|
def test_create_backend_native_x11_prefers_pynput(self):
|
|
"""Outside Flatpak on X11, pynput remains the default."""
|
|
from vocalinux.ui.keyboard_backends import create_backend
|
|
|
|
env = {"XDG_SESSION_TYPE": "x11", "DISPLAY": ":0"}
|
|
with patch.dict(os.environ, env, clear=True):
|
|
with (
|
|
patch.object(kb_module, "EVDEV_AVAILABLE", True),
|
|
patch.object(kb_module, "EvdevKeyboardBackend") as mock_evdev,
|
|
patch.object(kb_module, "PYNPUT_AVAILABLE", True),
|
|
patch.object(
|
|
kb_module, "PynputKeyboardBackend", return_value=MagicMock()
|
|
) as mock_pynput,
|
|
):
|
|
backend = create_backend()
|
|
assert backend is mock_pynput.return_value
|
|
mock_pynput.assert_called_once()
|
|
mock_evdev.assert_not_called()
|
|
|
|
def test_create_backend_flatpak_native_x11_prefers_pynput(self) -> None:
|
|
"""Flatpak on native X11 uses pynput outside GNOME."""
|
|
env = {
|
|
"FLATPAK_ID": "com.vocalinux.Vocalinux",
|
|
"XDG_SESSION_TYPE": "x11",
|
|
"XDG_CURRENT_DESKTOP": "X-Cinnamon",
|
|
"DISPLAY": ":0",
|
|
}
|
|
fake = MagicMock()
|
|
with patch.dict(os.environ, env, clear=True):
|
|
with (
|
|
patch.object(kb_module, "EVDEV_AVAILABLE", True),
|
|
patch.object(kb_module, "EvdevKeyboardBackend") as mock_evdev,
|
|
patch.object(kb_module, "PYNPUT_AVAILABLE", True),
|
|
patch.object(kb_module, "PynputKeyboardBackend", return_value=fake) as mock_pynput,
|
|
):
|
|
backend = create_backend()
|
|
assert backend is fake
|
|
mock_pynput.assert_called_once()
|
|
mock_evdev.assert_not_called()
|
|
|
|
def test_create_backend_flatpak_gnome_native_x11_prefers_evdev(self) -> None:
|
|
"""Flatpak on GNOME X11 prefers evdev."""
|
|
env = {
|
|
"FLATPAK_ID": "com.vocalinux.Vocalinux",
|
|
"XDG_SESSION_TYPE": "x11",
|
|
"XDG_CURRENT_DESKTOP": "GNOME",
|
|
"DISPLAY": ":0",
|
|
}
|
|
fake = MagicMock()
|
|
fake.is_available.return_value = True
|
|
with patch.dict(os.environ, env, clear=True):
|
|
with (
|
|
patch.object(kb_module, "EVDEV_AVAILABLE", True),
|
|
patch.object(kb_module, "EvdevKeyboardBackend", return_value=fake) as mock_evdev,
|
|
patch.object(kb_module, "PYNPUT_AVAILABLE", True),
|
|
patch.object(kb_module, "PynputKeyboardBackend") as mock_pynput,
|
|
):
|
|
backend = create_backend()
|
|
assert backend is fake
|
|
mock_evdev.assert_called_once()
|
|
mock_pynput.assert_not_called()
|
|
|
|
def test_create_backend_flatpak_gnome_native_x11_falls_back_to_pynput(self) -> None:
|
|
"""Flatpak on GNOME X11 falls back when evdev cannot read /dev/input."""
|
|
env = {
|
|
"FLATPAK_ID": "com.vocalinux.Vocalinux",
|
|
"XDG_SESSION_TYPE": "x11",
|
|
"XDG_CURRENT_DESKTOP": "GNOME",
|
|
"DISPLAY": ":0",
|
|
}
|
|
fake = MagicMock()
|
|
fake.is_available.return_value = False
|
|
fake.get_permission_hint.return_value = "not in input group"
|
|
pynput_backend = MagicMock()
|
|
with patch.dict(os.environ, env, clear=True):
|
|
with (
|
|
patch.object(kb_module, "EVDEV_AVAILABLE", True),
|
|
patch.object(kb_module, "EvdevKeyboardBackend", return_value=fake),
|
|
patch.object(kb_module, "PYNPUT_AVAILABLE", True),
|
|
patch.object(
|
|
kb_module, "PynputKeyboardBackend", return_value=pynput_backend
|
|
) as mock_pynput,
|
|
):
|
|
backend = create_backend()
|
|
assert backend is pynput_backend
|
|
mock_pynput.assert_called_once()
|
|
|
|
def test_create_backend_flatpak_x11_with_wayland_display_prefers_evdev(self) -> None:
|
|
"""A Wayland socket keeps Flatpak on the evdev path."""
|
|
env = {
|
|
"FLATPAK_ID": "com.vocalinux.Vocalinux",
|
|
"XDG_SESSION_TYPE": "x11",
|
|
"WAYLAND_DISPLAY": "wayland-0",
|
|
"DISPLAY": ":0",
|
|
}
|
|
fake = MagicMock()
|
|
fake.is_available.return_value = True
|
|
with patch.dict(os.environ, env, clear=True):
|
|
with (
|
|
patch.object(kb_module, "EVDEV_AVAILABLE", True),
|
|
patch.object(kb_module, "EvdevKeyboardBackend", return_value=fake) as mock_evdev,
|
|
patch.object(kb_module, "PYNPUT_AVAILABLE", True),
|
|
patch.object(kb_module, "PynputKeyboardBackend") as mock_pynput,
|
|
):
|
|
backend = create_backend()
|
|
assert backend is fake
|
|
mock_evdev.assert_called_once()
|
|
mock_pynput.assert_not_called()
|
|
|
|
def test_create_backend_flatpak_xwayland_without_evdev_returns_none(self) -> None:
|
|
"""A Flatpak on a Wayland session without input access needs evdev."""
|
|
env = {
|
|
"FLATPAK_ID": "com.vocalinux.Vocalinux",
|
|
"XDG_SESSION_TYPE": "wayland",
|
|
"DISPLAY": ":0",
|
|
}
|
|
fake = MagicMock()
|
|
fake.is_available.return_value = False
|
|
fake.get_permission_hint.return_value = "not in input group"
|
|
with patch.dict(os.environ, env, clear=True):
|
|
with (
|
|
patch.object(kb_module, "EVDEV_AVAILABLE", True),
|
|
patch.object(kb_module, "EvdevKeyboardBackend", return_value=fake) as mock_evdev,
|
|
patch.object(kb_module, "PYNPUT_AVAILABLE", True),
|
|
patch.object(kb_module, "PynputKeyboardBackend") as mock_pynput,
|
|
):
|
|
backend = create_backend()
|
|
assert backend is None
|
|
mock_evdev.assert_called_once()
|
|
mock_pynput.assert_not_called()
|
|
|
|
def test_create_backend_flatpak_unknown_session_prefers_evdev(self) -> None:
|
|
"""Unknown Flatpak sessions conservatively prefer evdev."""
|
|
env = {
|
|
"FLATPAK_ID": "com.vocalinux.Vocalinux",
|
|
"DISPLAY": ":0",
|
|
}
|
|
fake = MagicMock()
|
|
fake.is_available.return_value = True
|
|
with patch.dict(os.environ, env, clear=True):
|
|
with (
|
|
patch.object(kb_module, "EVDEV_AVAILABLE", True),
|
|
patch.object(kb_module, "EvdevKeyboardBackend", return_value=fake) as mock_evdev,
|
|
patch.object(kb_module, "PYNPUT_AVAILABLE", True),
|
|
patch.object(kb_module, "PynputKeyboardBackend") as mock_pynput,
|
|
):
|
|
backend = create_backend()
|
|
assert backend is fake
|
|
mock_evdev.assert_called_once()
|
|
mock_pynput.assert_not_called()
|
|
|
|
def test_is_native_x11_session(self) -> None:
|
|
"""Raw x11 session type without a Wayland socket is native X11."""
|
|
with patch.dict(os.environ, {"XDG_SESSION_TYPE": "x11"}, clear=True):
|
|
assert _is_native_x11_session()
|
|
|
|
def test_is_native_x11_session_case_insensitive(self) -> None:
|
|
"""Raw XDG_SESSION_TYPE is case-insensitive."""
|
|
with patch.dict(os.environ, {"XDG_SESSION_TYPE": "X11"}, clear=True):
|
|
assert _is_native_x11_session()
|
|
|
|
def test_is_native_x11_session_with_wayland_display(self) -> None:
|
|
"""A Wayland socket means the X11 display may be XWayland."""
|
|
env = {"XDG_SESSION_TYPE": "x11", "WAYLAND_DISPLAY": "wayland-0"}
|
|
with patch.dict(os.environ, env, clear=True):
|
|
assert not _is_native_x11_session()
|
|
|
|
def test_is_native_x11_session_wayland(self) -> None:
|
|
"""A Wayland session is not native X11."""
|
|
with patch.dict(os.environ, {"XDG_SESSION_TYPE": "wayland"}, clear=True):
|
|
assert not _is_native_x11_session()
|
|
|
|
def test_is_native_x11_session_unset(self) -> None:
|
|
"""An unset session type is not treated as native X11."""
|
|
with patch.dict(os.environ, {}, clear=True):
|
|
assert not _is_native_x11_session()
|
|
|
|
def test_create_backend_gnome_x11_prefers_evdev(self) -> None:
|
|
"""GNOME X11 must prefer evdev: mutter's XRecord drops physical key events (#954)."""
|
|
from vocalinux.ui.keyboard_backends import create_backend
|
|
|
|
fake = MagicMock()
|
|
fake.is_available.return_value = True
|
|
|
|
env = {
|
|
"XDG_SESSION_TYPE": "x11",
|
|
"XDG_CURRENT_DESKTOP": "GNOME",
|
|
"DISPLAY": ":0",
|
|
}
|
|
with patch.dict(os.environ, env, clear=True):
|
|
with (
|
|
patch.object(kb_module, "EVDEV_AVAILABLE", True),
|
|
patch.object(kb_module, "EvdevKeyboardBackend", return_value=fake) as mock_evdev,
|
|
patch.object(kb_module, "PYNPUT_AVAILABLE", True),
|
|
patch.object(kb_module, "PynputKeyboardBackend") as mock_pynput,
|
|
):
|
|
backend = create_backend()
|
|
assert backend is fake
|
|
mock_evdev.assert_called_once()
|
|
mock_pynput.assert_not_called()
|
|
|
|
def test_create_backend_gnome_x11_falls_back_to_pynput(self) -> None:
|
|
"""GNOME X11 keeps pynput when evdev cannot read /dev/input (#954)."""
|
|
from vocalinux.ui.keyboard_backends import create_backend
|
|
|
|
fake = MagicMock()
|
|
fake.is_available.return_value = False
|
|
fake.get_permission_hint.return_value = "not in input group"
|
|
|
|
env = {
|
|
"XDG_SESSION_TYPE": "x11",
|
|
"XDG_CURRENT_DESKTOP": "ubuntu:GNOME",
|
|
"DISPLAY": ":0",
|
|
}
|
|
with patch.dict(os.environ, env, clear=True):
|
|
with (
|
|
patch.object(kb_module, "EVDEV_AVAILABLE", True),
|
|
patch.object(kb_module, "EvdevKeyboardBackend", return_value=fake),
|
|
patch.object(kb_module, "PYNPUT_AVAILABLE", True),
|
|
patch.object(
|
|
kb_module, "PynputKeyboardBackend", return_value=MagicMock()
|
|
) as mock_pynput,
|
|
):
|
|
backend = create_backend()
|
|
assert backend is mock_pynput.return_value
|
|
mock_pynput.assert_called_once()
|
|
|
|
def test_detect_xdg_session_type_case_insensitive(self):
|
|
"""Test that XDG_SESSION_TYPE detection is case-insensitive."""
|
|
with patch.dict(os.environ, {"XDG_SESSION_TYPE": "WAYLAND"}, clear=False):
|
|
result = DesktopEnvironment.detect()
|
|
assert result == DesktopEnvironment.WAYLAND
|
|
|
|
def test_detect_xdg_session_type_mixed_case(self):
|
|
"""Test that XDG_SESSION_TYPE detection handles mixed case."""
|
|
with patch.dict(os.environ, {"XDG_SESSION_TYPE": "Wayland"}, clear=False):
|
|
result = DesktopEnvironment.detect()
|
|
assert result == DesktopEnvironment.WAYLAND
|
|
|
|
def test_detect_wayland_display_env_var(self):
|
|
"""Test detection of Wayland via WAYLAND_DISPLAY env var (fallback)."""
|
|
env = {
|
|
"WAYLAND_DISPLAY": "wayland-0",
|
|
}
|
|
with patch.dict(os.environ, env, clear=True):
|
|
result = DesktopEnvironment.detect()
|
|
assert result == DesktopEnvironment.WAYLAND
|
|
|
|
def test_detect_display_env_var(self):
|
|
"""Test detection of X11 via DISPLAY env var (fallback)."""
|
|
env = {
|
|
"DISPLAY": ":0",
|
|
}
|
|
with patch.dict(os.environ, env, clear=True):
|
|
result = DesktopEnvironment.detect()
|
|
assert result == DesktopEnvironment.X11
|
|
|
|
def test_detect_wayland_display_priority_over_display(self):
|
|
"""Test that WAYLAND_DISPLAY takes priority over DISPLAY."""
|
|
env = {
|
|
"WAYLAND_DISPLAY": "wayland-0",
|
|
"DISPLAY": ":0",
|
|
}
|
|
with patch.dict(os.environ, env, clear=True):
|
|
result = DesktopEnvironment.detect()
|
|
assert result == DesktopEnvironment.WAYLAND
|
|
|
|
def test_detect_xdg_session_type_priority_over_env_vars(self):
|
|
"""Test that XDG_SESSION_TYPE takes priority over WAYLAND_DISPLAY/DISPLAY."""
|
|
env = {
|
|
"XDG_SESSION_TYPE": "x11",
|
|
"WAYLAND_DISPLAY": "wayland-0",
|
|
}
|
|
with patch.dict(os.environ, env, clear=True):
|
|
result = DesktopEnvironment.detect()
|
|
assert result == DesktopEnvironment.X11
|
|
|
|
def test_detect_unknown_no_env_vars(self):
|
|
"""Test detection returns unknown when no env vars are set."""
|
|
with patch.dict(os.environ, {}, clear=True):
|
|
result = DesktopEnvironment.detect()
|
|
assert result == DesktopEnvironment.UNKNOWN
|
|
|
|
def test_detect_unknown_xdg_session_type_unknown(self):
|
|
"""Test detection returns unknown for unknown XDG_SESSION_TYPE."""
|
|
with patch.dict(os.environ, {"XDG_SESSION_TYPE": "unknown_session"}, clear=True):
|
|
result = DesktopEnvironment.detect()
|
|
assert result == DesktopEnvironment.UNKNOWN
|
|
|
|
def test_constants_defined(self):
|
|
"""Test that DesktopEnvironment constants are defined."""
|
|
assert DesktopEnvironment.X11 == "x11"
|
|
assert DesktopEnvironment.WAYLAND == "wayland"
|
|
assert DesktopEnvironment.UNKNOWN == "unknown"
|
|
|
|
|
|
class TestCreateBackendPreferred:
|
|
"""Test create_backend() with preferred backend specified."""
|
|
|
|
def test_create_backend_preferred_evdev_available_and_usable(self):
|
|
"""Test preferred evdev backend when available and usable."""
|
|
mock_evdev_backend = MagicMock()
|
|
mock_evdev_backend.is_available.return_value = True
|
|
|
|
with patch("vocalinux.ui.keyboard_backends.EVDEV_AVAILABLE", True):
|
|
with patch(
|
|
"vocalinux.ui.keyboard_backends.EvdevKeyboardBackend",
|
|
return_value=mock_evdev_backend,
|
|
):
|
|
result = create_backend(preferred_backend="evdev")
|
|
assert result == mock_evdev_backend
|
|
mock_evdev_backend.is_available.assert_called_once()
|
|
|
|
def test_create_backend_preferred_evdev_not_available(self):
|
|
"""Test preferred evdev backend when module not available."""
|
|
with patch("vocalinux.ui.keyboard_backends.EVDEV_AVAILABLE", False):
|
|
with patch("vocalinux.ui.keyboard_backends.PYNPUT_AVAILABLE", False):
|
|
result = create_backend(preferred_backend="evdev")
|
|
assert result is None
|
|
|
|
def test_create_backend_preferred_evdev_available_but_not_usable(self):
|
|
"""Test preferred evdev backend when available but not usable."""
|
|
mock_evdev_backend = MagicMock()
|
|
mock_evdev_backend.is_available.return_value = False
|
|
mock_evdev_backend.get_permission_hint.return_value = "Permission denied"
|
|
|
|
with patch("vocalinux.ui.keyboard_backends.EVDEV_AVAILABLE", True):
|
|
with patch(
|
|
"vocalinux.ui.keyboard_backends.EvdevKeyboardBackend",
|
|
return_value=mock_evdev_backend,
|
|
):
|
|
with patch("vocalinux.ui.keyboard_backends.PYNPUT_AVAILABLE", False):
|
|
result = create_backend(preferred_backend="evdev")
|
|
assert result is None
|
|
|
|
def test_create_backend_preferred_pynput_available(self):
|
|
"""Test preferred pynput backend when available."""
|
|
mock_pynput_backend = MagicMock()
|
|
|
|
with patch("vocalinux.ui.keyboard_backends.PYNPUT_AVAILABLE", True):
|
|
with patch(
|
|
"vocalinux.ui.keyboard_backends.PynputKeyboardBackend",
|
|
return_value=mock_pynput_backend,
|
|
):
|
|
result = create_backend(preferred_backend="pynput")
|
|
assert result == mock_pynput_backend
|
|
|
|
def test_create_backend_preferred_pynput_not_available(self):
|
|
"""Test preferred pynput backend when not available."""
|
|
with patch("vocalinux.ui.keyboard_backends.PYNPUT_AVAILABLE", False):
|
|
with patch("vocalinux.ui.keyboard_backends.EVDEV_AVAILABLE", False):
|
|
result = create_backend(preferred_backend="pynput")
|
|
assert result is None
|
|
|
|
def test_create_backend_preferred_unknown_backend(self):
|
|
"""Test create_backend with unknown preferred backend."""
|
|
with patch("vocalinux.ui.keyboard_backends.EVDEV_AVAILABLE", False):
|
|
with patch("vocalinux.ui.keyboard_backends.PYNPUT_AVAILABLE", False):
|
|
result = create_backend(preferred_backend="unknown_backend")
|
|
assert result is None
|
|
|
|
def test_create_backend_preferred_with_custom_shortcut(self):
|
|
"""Test that shortcut parameter is passed to backend."""
|
|
mock_pynput_backend = MagicMock()
|
|
|
|
with patch("vocalinux.ui.keyboard_backends.PYNPUT_AVAILABLE", True):
|
|
with patch(
|
|
"vocalinux.ui.keyboard_backends.PynputKeyboardBackend",
|
|
return_value=mock_pynput_backend,
|
|
) as mock_constructor:
|
|
result = create_backend(
|
|
preferred_backend="pynput", shortcut="alt+alt", mode="push_to_talk"
|
|
)
|
|
mock_constructor.assert_called_once_with(shortcut="alt+alt", mode="push_to_talk")
|
|
assert result == mock_pynput_backend
|
|
|
|
def test_create_backend_preferred_with_custom_mode(self):
|
|
"""Test that mode parameter is passed to backend."""
|
|
mock_evdev_backend = MagicMock()
|
|
mock_evdev_backend.is_available.return_value = True
|
|
|
|
with patch("vocalinux.ui.keyboard_backends.EVDEV_AVAILABLE", True):
|
|
with patch(
|
|
"vocalinux.ui.keyboard_backends.EvdevKeyboardBackend",
|
|
return_value=mock_evdev_backend,
|
|
) as mock_constructor:
|
|
result = create_backend(preferred_backend="evdev", mode="push_to_talk")
|
|
mock_constructor.assert_called_once()
|
|
call_kwargs = mock_constructor.call_args[1]
|
|
assert call_kwargs["mode"] == "push_to_talk"
|
|
|
|
|
|
class TestCreateBackendAutoDetect:
|
|
"""Test create_backend() with auto-detection based on desktop environment."""
|
|
|
|
def test_create_backend_wayland_evdev_available_and_usable(self):
|
|
"""Test Wayland environment auto-selects evdev when available."""
|
|
mock_evdev_backend = MagicMock()
|
|
mock_evdev_backend.is_available.return_value = True
|
|
|
|
with patch.dict(os.environ, {"XDG_SESSION_TYPE": "wayland"}, clear=True):
|
|
with patch("vocalinux.ui.keyboard_backends.EVDEV_AVAILABLE", True):
|
|
with patch(
|
|
"vocalinux.ui.keyboard_backends.EvdevKeyboardBackend",
|
|
return_value=mock_evdev_backend,
|
|
):
|
|
with patch("vocalinux.ui.keyboard_backends.PYNPUT_AVAILABLE", False):
|
|
result = create_backend()
|
|
assert result == mock_evdev_backend
|
|
|
|
def test_create_backend_wayland_evdev_not_available(self):
|
|
"""Test Wayland returns None when evdev not available."""
|
|
with patch.dict(os.environ, {"XDG_SESSION_TYPE": "wayland"}, clear=True):
|
|
with patch("vocalinux.ui.keyboard_backends.EVDEV_AVAILABLE", False):
|
|
with patch("vocalinux.ui.keyboard_backends.PYNPUT_AVAILABLE", False):
|
|
result = create_backend()
|
|
assert result is None
|
|
|
|
def test_create_backend_wayland_evdev_available_but_not_usable(self):
|
|
"""Test Wayland returns None when evdev not usable."""
|
|
mock_evdev_backend = MagicMock()
|
|
mock_evdev_backend.is_available.return_value = False
|
|
mock_evdev_backend.get_permission_hint.return_value = "Permission denied"
|
|
|
|
with patch.dict(os.environ, {"XDG_SESSION_TYPE": "wayland"}, clear=True):
|
|
with patch("vocalinux.ui.keyboard_backends.EVDEV_AVAILABLE", True):
|
|
with patch(
|
|
"vocalinux.ui.keyboard_backends.EvdevKeyboardBackend",
|
|
return_value=mock_evdev_backend,
|
|
):
|
|
result = create_backend()
|
|
assert result is None
|
|
|
|
def test_create_backend_x11_pynput_available(self):
|
|
"""Test X11 environment auto-selects pynput."""
|
|
mock_pynput_backend = MagicMock()
|
|
|
|
with patch.dict(os.environ, {"XDG_SESSION_TYPE": "x11"}, clear=True):
|
|
with patch("vocalinux.ui.keyboard_backends.PYNPUT_AVAILABLE", True):
|
|
with patch(
|
|
"vocalinux.ui.keyboard_backends.PynputKeyboardBackend",
|
|
return_value=mock_pynput_backend,
|
|
):
|
|
result = create_backend()
|
|
assert result == mock_pynput_backend
|
|
|
|
def test_create_backend_x11_pynput_not_available_evdev_available(self):
|
|
"""Test X11 falls back to evdev when pynput not available."""
|
|
mock_evdev_backend = MagicMock()
|
|
mock_evdev_backend.is_available.return_value = True
|
|
|
|
with patch.dict(os.environ, {"XDG_SESSION_TYPE": "x11"}, clear=True):
|
|
with patch("vocalinux.ui.keyboard_backends.PYNPUT_AVAILABLE", False):
|
|
with patch("vocalinux.ui.keyboard_backends.EVDEV_AVAILABLE", True):
|
|
with patch(
|
|
"vocalinux.ui.keyboard_backends.EvdevKeyboardBackend",
|
|
return_value=mock_evdev_backend,
|
|
):
|
|
result = create_backend()
|
|
assert result == mock_evdev_backend
|
|
|
|
def test_create_backend_unknown_env_pynput_available(self):
|
|
"""Test unknown environment defaults to pynput."""
|
|
mock_pynput_backend = MagicMock()
|
|
|
|
with patch.dict(os.environ, {}, clear=True):
|
|
with patch("vocalinux.ui.keyboard_backends.PYNPUT_AVAILABLE", True):
|
|
with patch(
|
|
"vocalinux.ui.keyboard_backends.PynputKeyboardBackend",
|
|
return_value=mock_pynput_backend,
|
|
):
|
|
result = create_backend()
|
|
assert result == mock_pynput_backend
|
|
|
|
def test_create_backend_unknown_env_no_backends(self):
|
|
"""Test unknown environment returns None when no backends available."""
|
|
with patch.dict(os.environ, {}, clear=True):
|
|
with patch("vocalinux.ui.keyboard_backends.PYNPUT_AVAILABLE", False):
|
|
with patch("vocalinux.ui.keyboard_backends.EVDEV_AVAILABLE", False):
|
|
result = create_backend()
|
|
assert result is None
|
|
|
|
def test_create_backend_no_backends_available(self):
|
|
"""Test create_backend returns None when no backends available at all."""
|
|
with patch("vocalinux.ui.keyboard_backends.PYNPUT_AVAILABLE", False):
|
|
with patch("vocalinux.ui.keyboard_backends.EVDEV_AVAILABLE", False):
|
|
result = create_backend()
|
|
assert result is None
|
|
|
|
def test_create_backend_with_custom_parameters(self):
|
|
"""Test that custom parameters are passed to auto-selected backend."""
|
|
mock_pynput_backend = MagicMock()
|
|
|
|
with patch.dict(os.environ, {"XDG_SESSION_TYPE": "x11"}, clear=True):
|
|
with patch("vocalinux.ui.keyboard_backends.PYNPUT_AVAILABLE", True):
|
|
with patch(
|
|
"vocalinux.ui.keyboard_backends.PynputKeyboardBackend",
|
|
return_value=mock_pynput_backend,
|
|
) as mock_constructor:
|
|
result = create_backend(shortcut="alt+alt", mode="push_to_talk")
|
|
mock_constructor.assert_called_once_with(
|
|
shortcut="alt+alt", mode="push_to_talk"
|
|
)
|
|
|
|
def test_create_backend_default_parameters(self):
|
|
"""Test that default parameters are used when not specified."""
|
|
mock_pynput_backend = MagicMock()
|
|
|
|
with patch.dict(os.environ, {"XDG_SESSION_TYPE": "x11"}, clear=True):
|
|
with patch("vocalinux.ui.keyboard_backends.PYNPUT_AVAILABLE", True):
|
|
with patch(
|
|
"vocalinux.ui.keyboard_backends.PynputKeyboardBackend",
|
|
return_value=mock_pynput_backend,
|
|
) as mock_constructor:
|
|
result = create_backend()
|
|
# Check that the constructor was called with some default shortcut/mode
|
|
mock_constructor.assert_called_once()
|
|
call_kwargs = mock_constructor.call_args[1]
|
|
# Shortcut and mode should be passed as defaults
|
|
assert "shortcut" in call_kwargs
|
|
assert "mode" in call_kwargs
|
|
|
|
|
|
class TestCreateBackendFallback:
|
|
"""Test create_backend() fallback scenarios."""
|
|
|
|
def test_create_backend_pynput_not_available_evdev_available_not_usable(self):
|
|
"""Test fallback to evdev when pynput unavailable and evdev not usable."""
|
|
mock_evdev_backend = MagicMock()
|
|
mock_evdev_backend.is_available.return_value = False
|
|
mock_evdev_backend.get_permission_hint.return_value = "Permission denied"
|
|
|
|
with patch.dict(os.environ, {"XDG_SESSION_TYPE": "x11"}, clear=True):
|
|
with patch("vocalinux.ui.keyboard_backends.PYNPUT_AVAILABLE", False):
|
|
with patch("vocalinux.ui.keyboard_backends.EVDEV_AVAILABLE", True):
|
|
with patch(
|
|
"vocalinux.ui.keyboard_backends.EvdevKeyboardBackend",
|
|
return_value=mock_evdev_backend,
|
|
):
|
|
result = create_backend()
|
|
assert result is None
|
|
|
|
def test_create_backend_wayland_preferred_pynput_fails_evdev_available(self):
|
|
"""Test that preferred pynput on Wayland fails and then tries evdev."""
|
|
# When preferred_backend is pynput and Wayland is detected,
|
|
# if pynput is not available, it falls back to auto-detection which tries evdev
|
|
mock_evdev_backend = MagicMock()
|
|
mock_evdev_backend.is_available.return_value = True
|
|
|
|
with patch.dict(os.environ, {"XDG_SESSION_TYPE": "wayland"}, clear=True):
|
|
with patch("vocalinux.ui.keyboard_backends.PYNPUT_AVAILABLE", False):
|
|
with patch("vocalinux.ui.keyboard_backends.EVDEV_AVAILABLE", True):
|
|
with patch(
|
|
"vocalinux.ui.keyboard_backends.EvdevKeyboardBackend",
|
|
return_value=mock_evdev_backend,
|
|
):
|
|
result = create_backend(preferred_backend="pynput")
|
|
# Falls back to auto-detection which selects evdev on Wayland
|
|
assert result == mock_evdev_backend
|