vibe-coded-mcp-dekstop-control/tests/test_layout_mapper.py
2026-06-25 22:57:37 +02:00

99 lines
3.2 KiB
Python

"""Tests for keyboard layout detection and character-to-evdev-keycode mapping."""
from unittest import mock
import pytest
from evdev import ecodes
from vibe_coded_mcp_desktop_control import layout_mapper
from vibe_coded_mcp_desktop_control.layout_mapper import (
detect_keyboard_layout,
get_keycode_for_char,
)
@pytest.fixture
def no_subprocess_layout(monkeypatch: pytest.MonkeyPatch) -> None:
"""Prevent subprocess-based layout detection from influencing tests."""
monkeypatch.delenv("XKB_DEFAULT_LAYOUT", raising=False)
with (
mock.patch.object(layout_mapper, "logger"),
mock.patch(
"vibe_coded_mcp_desktop_control.layout_mapper.subprocess.run",
return_value=mock.Mock(returncode=1, stdout="", stderr=""),
),
):
yield
def test_config_layout_takes_precedence(no_subprocess_layout: None) -> None:
"""An explicitly provided layout should be returned without further detection."""
assert detect_keyboard_layout("de") == "de"
def test_environment_variable_detection(
monkeypatch: pytest.MonkeyPatch, no_subprocess_layout: None
) -> None:
"""XKB_DEFAULT_LAYOUT should be used when no config layout is provided."""
monkeypatch.setenv("XKB_DEFAULT_LAYOUT", "fr,us")
assert detect_keyboard_layout(None) == "fr"
def test_environment_variable_detection_single(
monkeypatch: pytest.MonkeyPatch, no_subprocess_layout: None
) -> None:
"""A single XKB_DEFAULT_LAYOUT value should be returned as-is."""
monkeypatch.setenv("XKB_DEFAULT_LAYOUT", "si")
assert detect_keyboard_layout(None) == "si"
def test_fallback_to_us(no_subprocess_layout: None) -> None:
"""When nothing else is available, the layout should fall back to 'us'."""
assert detect_keyboard_layout(None) == "us"
def test_us_lowercase_letter() -> None:
"""A basic US lowercase letter should map to its keycode without modifiers."""
keycode, shift, altgr = get_keycode_for_char("a", "us")
assert keycode == ecodes.KEY_A
assert shift is False
assert altgr is False
def test_us_uppercase_letter() -> None:
"""A basic US uppercase letter should require Shift."""
keycode, shift, altgr = get_keycode_for_char("A", "us")
assert keycode == ecodes.KEY_A
assert shift is True
assert altgr is False
def test_us_digit_requires_shift() -> None:
"""US shifted digits should require Shift."""
keycode, shift, altgr = get_keycode_for_char("!", "us")
assert keycode == ecodes.KEY_1
assert shift is True
assert altgr is False
def test_si_altgr_character() -> None:
"""Slovenian AltGr characters should require the AltGr modifier."""
keycode, shift, altgr = get_keycode_for_char("@", "si")
assert keycode == ecodes.KEY_V
assert shift is False
assert altgr is True
def test_si_yz_swap() -> None:
"""Slovenian layout swaps the Y and Z physical keys."""
y_keycode, _, _ = get_keycode_for_char("y", "si")
z_keycode, _, _ = get_keycode_for_char("z", "si")
assert y_keycode == ecodes.KEY_Z
assert z_keycode == ecodes.KEY_Y
def test_unknown_character_raises() -> None:
"""Characters not present in any static map should raise ValueError."""
with pytest.raises(ValueError):
get_keycode_for_char("\x00", "us")