"""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")