chore(repo): baseline du fork input-remapper (upstream v2.2.1)
Le dépôt ne contenait que README.md ; ajout de l'intégralité du code fonctionnel du fork comme socle stable de la branche de release. L'état runtime d'orchestration (.ideai/) est exclu du suivi. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
559
tests/unit/test_input_config.py
Normal file
559
tests/unit/test_input_config.py
Normal file
@ -0,0 +1,559 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
# input-remapper - GUI for device specific keyboard mappings
|
||||
# Copyright (C) 2025 sezanzeb <b8x45ygc9@mozmail.com>
|
||||
#
|
||||
# This file is part of input-remapper.
|
||||
#
|
||||
# input-remapper is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# input-remapper is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with input-remapper. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
import unittest
|
||||
|
||||
from evdev.ecodes import (
|
||||
EV_KEY,
|
||||
EV_ABS,
|
||||
EV_REL,
|
||||
BTN_C,
|
||||
BTN_B,
|
||||
BTN_A,
|
||||
BTN_LEFT,
|
||||
BTN_MIDDLE,
|
||||
BTN_RIGHT,
|
||||
BTN_SIDE,
|
||||
BTN_EXTRA,
|
||||
BTN_FORWARD,
|
||||
BTN_BACK,
|
||||
BTN_TASK,
|
||||
REL_X,
|
||||
REL_Y,
|
||||
REL_WHEEL,
|
||||
REL_HWHEEL,
|
||||
ABS_RY,
|
||||
ABS_X,
|
||||
ABS_HAT0Y,
|
||||
ABS_HAT0X,
|
||||
KEY_A,
|
||||
KEY_LEFTSHIFT,
|
||||
KEY_RIGHTALT,
|
||||
KEY_LEFTCTRL,
|
||||
)
|
||||
|
||||
from inputremapper.configs.input_config import InputCombination, InputConfig
|
||||
from tests.lib.test_setup import test_setup
|
||||
|
||||
|
||||
@test_setup
|
||||
class TestInputConfig(unittest.TestCase):
|
||||
def test_input_config(self):
|
||||
test_cases = [
|
||||
# basic test, nothing fancy here
|
||||
{
|
||||
"input": {
|
||||
"type": EV_KEY,
|
||||
"code": KEY_A,
|
||||
"origin_hash": "foo",
|
||||
},
|
||||
"properties": {
|
||||
"type": EV_KEY,
|
||||
"code": KEY_A,
|
||||
"origin_hash": "foo",
|
||||
"input_match_hash": (EV_KEY, KEY_A, "foo"),
|
||||
"defines_analog_input": False,
|
||||
"type_and_code": (EV_KEY, KEY_A),
|
||||
},
|
||||
"methods": [
|
||||
{
|
||||
"name": "description",
|
||||
"args": (),
|
||||
"kwargs": {},
|
||||
"return": "a",
|
||||
},
|
||||
{
|
||||
"name": "__hash__",
|
||||
"args": (),
|
||||
"kwargs": {},
|
||||
"return": hash((EV_KEY, KEY_A, "foo", None)),
|
||||
},
|
||||
],
|
||||
},
|
||||
# removes analog_threshold
|
||||
{
|
||||
"input": {
|
||||
"type": EV_KEY,
|
||||
"code": KEY_A,
|
||||
"origin_hash": "foo",
|
||||
"analog_threshold": 10,
|
||||
},
|
||||
"properties": {
|
||||
"type": EV_KEY,
|
||||
"code": KEY_A,
|
||||
"origin_hash": "foo",
|
||||
"analog_threshold": None,
|
||||
"input_match_hash": (EV_KEY, KEY_A, "foo"),
|
||||
"defines_analog_input": False,
|
||||
"type_and_code": (EV_KEY, KEY_A),
|
||||
},
|
||||
"methods": [
|
||||
{
|
||||
"name": "description",
|
||||
"args": (),
|
||||
"kwargs": {},
|
||||
"return": "a",
|
||||
},
|
||||
{
|
||||
"name": "__hash__",
|
||||
"args": (),
|
||||
"kwargs": {},
|
||||
"return": hash((EV_KEY, KEY_A, "foo", None)),
|
||||
},
|
||||
],
|
||||
},
|
||||
# abs to btn
|
||||
{
|
||||
"input": {
|
||||
"type": EV_ABS,
|
||||
"code": ABS_X,
|
||||
"origin_hash": "foo",
|
||||
"analog_threshold": 10,
|
||||
},
|
||||
"properties": {
|
||||
"type": EV_ABS,
|
||||
"code": ABS_X,
|
||||
"origin_hash": "foo",
|
||||
"analog_threshold": 10,
|
||||
"input_match_hash": (EV_ABS, ABS_X, "foo"),
|
||||
"defines_analog_input": False,
|
||||
"type_and_code": (EV_ABS, ABS_X),
|
||||
},
|
||||
"methods": [
|
||||
{
|
||||
"name": "description",
|
||||
"args": (),
|
||||
"kwargs": {},
|
||||
"return": "Joystick-X Right 10%",
|
||||
},
|
||||
{
|
||||
"name": "description",
|
||||
"args": (),
|
||||
"kwargs": {"exclude_threshold": True},
|
||||
"return": "Joystick-X Right",
|
||||
},
|
||||
{
|
||||
"name": "description",
|
||||
"args": (),
|
||||
"kwargs": {
|
||||
"exclude_threshold": True,
|
||||
"exclude_direction": True,
|
||||
},
|
||||
"return": "Joystick-X",
|
||||
},
|
||||
{
|
||||
"name": "__hash__",
|
||||
"args": (),
|
||||
"kwargs": {},
|
||||
"return": hash((EV_ABS, ABS_X, "foo", 10)),
|
||||
},
|
||||
],
|
||||
},
|
||||
# abs to btn with d-pad
|
||||
{
|
||||
"input": {
|
||||
"type": EV_ABS,
|
||||
"code": ABS_HAT0Y,
|
||||
"origin_hash": "foo",
|
||||
"analog_threshold": 10,
|
||||
},
|
||||
"properties": {
|
||||
"type": EV_ABS,
|
||||
"code": ABS_HAT0Y,
|
||||
"origin_hash": "foo",
|
||||
"analog_threshold": 10,
|
||||
"input_match_hash": (EV_ABS, ABS_HAT0Y, "foo"),
|
||||
"defines_analog_input": False,
|
||||
"type_and_code": (EV_ABS, ABS_HAT0Y),
|
||||
},
|
||||
"methods": [
|
||||
{
|
||||
"name": "description",
|
||||
"args": (),
|
||||
"kwargs": {},
|
||||
"return": "DPad-Y Down 10%",
|
||||
},
|
||||
{
|
||||
"name": "__hash__",
|
||||
"args": (),
|
||||
"kwargs": {},
|
||||
"return": hash((EV_ABS, ABS_HAT0Y, "foo", 10)),
|
||||
},
|
||||
],
|
||||
},
|
||||
# rel to btn
|
||||
{
|
||||
"input": {
|
||||
"type": EV_REL,
|
||||
"code": REL_Y,
|
||||
"origin_hash": "foo",
|
||||
"analog_threshold": 10,
|
||||
},
|
||||
"properties": {
|
||||
"type": EV_REL,
|
||||
"code": REL_Y,
|
||||
"origin_hash": "foo",
|
||||
"analog_threshold": 10,
|
||||
"input_match_hash": (EV_REL, REL_Y, "foo"),
|
||||
"defines_analog_input": False,
|
||||
"type_and_code": (EV_REL, REL_Y),
|
||||
},
|
||||
"methods": [
|
||||
{
|
||||
"name": "description",
|
||||
"args": (),
|
||||
"kwargs": {},
|
||||
"return": "Y Down 10",
|
||||
},
|
||||
{
|
||||
"name": "__hash__",
|
||||
"args": (),
|
||||
"kwargs": {},
|
||||
"return": hash((EV_REL, REL_Y, "foo", 10)),
|
||||
},
|
||||
],
|
||||
},
|
||||
# abs as axis
|
||||
{
|
||||
"input": {
|
||||
"type": EV_ABS,
|
||||
"code": ABS_X,
|
||||
"origin_hash": "foo",
|
||||
"analog_threshold": 0,
|
||||
},
|
||||
"properties": {
|
||||
"type": EV_ABS,
|
||||
"code": ABS_X,
|
||||
"origin_hash": "foo",
|
||||
"analog_threshold": None,
|
||||
"input_match_hash": (EV_ABS, ABS_X, "foo"),
|
||||
"defines_analog_input": True,
|
||||
"type_and_code": (EV_ABS, ABS_X),
|
||||
},
|
||||
"methods": [
|
||||
{
|
||||
"name": "description",
|
||||
"args": (),
|
||||
"kwargs": {},
|
||||
"return": "Joystick-X",
|
||||
},
|
||||
{
|
||||
"name": "description",
|
||||
"args": (),
|
||||
"kwargs": {
|
||||
"exclude_threshold": True,
|
||||
"exclude_direction": True,
|
||||
},
|
||||
"return": "Joystick-X",
|
||||
},
|
||||
{
|
||||
"name": "__hash__",
|
||||
"args": (),
|
||||
"kwargs": {},
|
||||
"return": hash((EV_ABS, ABS_X, "foo", None)),
|
||||
},
|
||||
],
|
||||
},
|
||||
# rel as axis
|
||||
{
|
||||
"input": {
|
||||
"type": EV_REL,
|
||||
"code": REL_WHEEL,
|
||||
"origin_hash": "foo",
|
||||
},
|
||||
"properties": {
|
||||
"type": EV_REL,
|
||||
"code": REL_WHEEL,
|
||||
"origin_hash": "foo",
|
||||
"analog_threshold": None,
|
||||
"input_match_hash": (EV_REL, REL_WHEEL, "foo"),
|
||||
"defines_analog_input": True,
|
||||
"type_and_code": (EV_REL, REL_WHEEL),
|
||||
},
|
||||
"methods": [
|
||||
{
|
||||
"name": "description",
|
||||
"args": (),
|
||||
"kwargs": {},
|
||||
"return": "Wheel",
|
||||
},
|
||||
{
|
||||
"name": "__hash__",
|
||||
"args": (),
|
||||
"kwargs": {},
|
||||
"return": hash((EV_REL, REL_WHEEL, "foo", None)),
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
for test_case in test_cases:
|
||||
input_config = InputConfig(**test_case["input"])
|
||||
for property_, value in test_case["properties"].items():
|
||||
self.assertEqual(
|
||||
value,
|
||||
getattr(input_config, property_),
|
||||
f"property mismatch for input: {test_case['input']} "
|
||||
f"property: {property_} expected value: {value}",
|
||||
)
|
||||
for method in test_case["methods"]:
|
||||
self.assertEqual(
|
||||
method["return"],
|
||||
getattr(input_config, method["name"])(
|
||||
*method["args"], **method["kwargs"]
|
||||
),
|
||||
f"wrong method return for input: {test_case['input']} "
|
||||
f"method: {method}",
|
||||
)
|
||||
|
||||
def test_is_immutable(self):
|
||||
input_config = InputConfig(type=1, code=2)
|
||||
with self.assertRaises(TypeError):
|
||||
input_config.origin_hash = "foo"
|
||||
|
||||
|
||||
@test_setup
|
||||
class TestInputCombination(unittest.TestCase):
|
||||
def test_eq(self):
|
||||
a = InputCombination(
|
||||
[
|
||||
InputConfig(type=EV_REL, code=REL_X, value=1, origin_hash="1234"),
|
||||
InputConfig(type=EV_KEY, code=KEY_A, value=1, origin_hash="abcd"),
|
||||
]
|
||||
)
|
||||
b = InputCombination(
|
||||
[
|
||||
InputConfig(type=EV_REL, code=REL_X, value=1, origin_hash="1234"),
|
||||
InputConfig(type=EV_KEY, code=KEY_A, value=1, origin_hash="abcd"),
|
||||
]
|
||||
)
|
||||
self.assertEqual(a, b)
|
||||
|
||||
def test_not_eq(self):
|
||||
a = InputCombination(
|
||||
[
|
||||
InputConfig(type=EV_REL, code=REL_X, value=1, origin_hash="2345"),
|
||||
InputConfig(type=EV_KEY, code=KEY_A, value=1, origin_hash="bcde"),
|
||||
]
|
||||
)
|
||||
b = InputCombination(
|
||||
[
|
||||
InputConfig(type=EV_REL, code=REL_X, value=1, origin_hash="1234"),
|
||||
InputConfig(type=EV_KEY, code=KEY_A, value=1, origin_hash="abcd"),
|
||||
]
|
||||
)
|
||||
self.assertNotEqual(a, b)
|
||||
|
||||
def test_can_be_used_as_dict_key(self):
|
||||
dict_ = {
|
||||
InputCombination(
|
||||
[
|
||||
InputConfig(type=EV_REL, code=REL_X, value=1, origin_hash="1234"),
|
||||
InputConfig(type=EV_KEY, code=KEY_A, value=1, origin_hash="abcd"),
|
||||
]
|
||||
): "foo"
|
||||
}
|
||||
key = InputCombination(
|
||||
[
|
||||
InputConfig(type=EV_REL, code=REL_X, value=1, origin_hash="1234"),
|
||||
InputConfig(type=EV_KEY, code=KEY_A, value=1, origin_hash="abcd"),
|
||||
]
|
||||
)
|
||||
self.assertEqual(dict_.get(key), "foo")
|
||||
|
||||
def test_get_permutations(self):
|
||||
key_1 = InputCombination(InputCombination.from_tuples((1, 3, 1)))
|
||||
self.assertEqual(len(key_1.get_permutations()), 1)
|
||||
self.assertEqual(key_1.get_permutations()[0], key_1)
|
||||
|
||||
key_2 = InputCombination(InputCombination.from_tuples((1, 3, 1), (1, 5, 1)))
|
||||
self.assertEqual(len(key_2.get_permutations()), 1)
|
||||
self.assertEqual(key_2.get_permutations()[0], key_2)
|
||||
|
||||
key_3 = InputCombination(
|
||||
InputCombination.from_tuples((1, 3, 1), (1, 5, 1), (1, 7, 1))
|
||||
)
|
||||
self.assertEqual(len(key_3.get_permutations()), 2)
|
||||
self.assertEqual(
|
||||
key_3.get_permutations()[0],
|
||||
InputCombination(
|
||||
InputCombination.from_tuples((1, 3, 1), (1, 5, 1), (1, 7, 1))
|
||||
),
|
||||
)
|
||||
self.assertEqual(
|
||||
key_3.get_permutations()[1],
|
||||
InputCombination(
|
||||
InputCombination.from_tuples((1, 5, 1), (1, 3, 1), (1, 7, 1))
|
||||
),
|
||||
)
|
||||
|
||||
def test_is_problematic(self):
|
||||
key_1 = InputCombination(
|
||||
InputCombination.from_tuples((1, KEY_LEFTSHIFT, 1), (1, 5, 1))
|
||||
)
|
||||
self.assertTrue(key_1.is_problematic())
|
||||
|
||||
key_2 = InputCombination(
|
||||
InputCombination.from_tuples((1, KEY_RIGHTALT, 1), (1, 5, 1))
|
||||
)
|
||||
self.assertTrue(key_2.is_problematic())
|
||||
|
||||
key_3 = InputCombination(
|
||||
InputCombination.from_tuples((1, 3, 1), (1, KEY_LEFTCTRL, 1))
|
||||
)
|
||||
self.assertTrue(key_3.is_problematic())
|
||||
|
||||
key_4 = InputCombination(InputCombination.from_tuples((1, 3, 1)))
|
||||
self.assertFalse(key_4.is_problematic())
|
||||
|
||||
key_5 = InputCombination(InputCombination.from_tuples((1, 3, 1), (1, 5, 1)))
|
||||
self.assertFalse(key_5.is_problematic())
|
||||
|
||||
def test_init(self):
|
||||
self.assertRaises(TypeError, lambda: InputCombination(1))
|
||||
self.assertRaises(TypeError, lambda: InputCombination(None))
|
||||
self.assertRaises(TypeError, lambda: InputCombination([1]))
|
||||
self.assertRaises(TypeError, lambda: InputCombination((1,)))
|
||||
self.assertRaises(TypeError, lambda: InputCombination((1, 2)))
|
||||
self.assertRaises(TypeError, lambda: InputCombination("1"))
|
||||
self.assertRaises(TypeError, lambda: InputCombination("(1,2,3)"))
|
||||
self.assertRaises(
|
||||
TypeError,
|
||||
lambda: InputCombination(((1, 2, 3), (1, 2, 3), None)),
|
||||
)
|
||||
|
||||
# those don't raise errors
|
||||
InputCombination(({"type": 1, "code": 2}, {"type": 1, "code": 1}))
|
||||
InputCombination(({"type": 1, "code": 2},))
|
||||
InputCombination(({"type": "1", "code": "2"},))
|
||||
InputCombination([InputConfig(type=1, code=2, analog_threshold=3)])
|
||||
InputCombination(
|
||||
(
|
||||
{"type": 1, "code": 2},
|
||||
{"type": "1", "code": "2"},
|
||||
InputConfig(type=1, code=2),
|
||||
)
|
||||
)
|
||||
|
||||
def test_to_config(self):
|
||||
c1 = InputCombination([InputConfig(type=1, code=2, analog_threshold=3)])
|
||||
c2 = InputCombination(
|
||||
(
|
||||
InputConfig(type=1, code=2, analog_threshold=3),
|
||||
InputConfig(type=4, code=5, analog_threshold=6),
|
||||
)
|
||||
)
|
||||
# analog_threshold is removed for key events
|
||||
self.assertEqual(c1.to_config(), ({"type": 1, "code": 2},))
|
||||
self.assertEqual(
|
||||
c2.to_config(),
|
||||
({"type": 1, "code": 2}, {"type": 4, "code": 5, "analog_threshold": 6}),
|
||||
)
|
||||
|
||||
def test_beautify(self):
|
||||
# not an integration test, but I have all the selection_label tests here already
|
||||
self.assert_beautify_single(EV_KEY, KEY_A, 1, "a")
|
||||
self.assert_beautify_single(EV_KEY, KEY_A, 1, "a")
|
||||
self.assert_beautify_single(EV_ABS, ABS_HAT0Y, -1, "DPad-Y Up")
|
||||
self.assert_beautify_single(EV_KEY, BTN_A, 1, "Button A")
|
||||
self.assert_beautify_single(EV_KEY, 1234, 1, "unknown (1, 1234)")
|
||||
self.assert_beautify_single(EV_ABS, ABS_HAT0X, -1, "DPad-X Left")
|
||||
self.assert_beautify_single(EV_ABS, ABS_HAT0Y, -1, "DPad-Y Up")
|
||||
self.assert_beautify_single(EV_KEY, BTN_A, 1, "Button A")
|
||||
self.assert_beautify_single(EV_ABS, ABS_X, 1, "Joystick-X Right")
|
||||
self.assert_beautify_single(EV_ABS, ABS_RY, 1, "Joystick-RY Down")
|
||||
self.assert_beautify_single(EV_REL, REL_HWHEEL, 1, "Wheel Right")
|
||||
self.assert_beautify_single(EV_REL, REL_WHEEL, -1, "Wheel Down")
|
||||
|
||||
# region "mouse buttons"
|
||||
self.assert_beautify_single(EV_KEY, BTN_LEFT, 1, "Mouse Button LEFT")
|
||||
self.assert_beautify_single(EV_KEY, BTN_MIDDLE, 1, "Mouse Button MIDDLE")
|
||||
self.assert_beautify_single(EV_KEY, BTN_RIGHT, 1, "Mouse Button RIGHT")
|
||||
self.assert_beautify_single(EV_KEY, BTN_SIDE, 1, "Mouse Button 4")
|
||||
self.assert_beautify_single(EV_KEY, BTN_EXTRA, 1, "Mouse Button 5")
|
||||
self.assert_beautify_single(EV_KEY, BTN_FORWARD, 1, "Mouse Button 6")
|
||||
self.assert_beautify_single(EV_KEY, BTN_BACK, 1, "Mouse Button 7")
|
||||
self.assert_beautify_single(EV_KEY, BTN_TASK, 1, "Mouse Button 8")
|
||||
|
||||
# Mouse buttons 9+ do not have an evdev name, and so must be expressed
|
||||
# as an offset from BTN_LEFT AKA "Button 1". Subtract 1 for base index
|
||||
# so that `btn_mouse_base + 1` would be "Button 1".
|
||||
btn_mouse_base: int = BTN_LEFT - 1
|
||||
|
||||
self.assert_beautify_single(EV_KEY, btn_mouse_base + 9, 1, "Mouse Button 9")
|
||||
self.assert_beautify_single(EV_KEY, btn_mouse_base + 10, 1, "Mouse Button 10")
|
||||
# endregion "mouse buttons"
|
||||
|
||||
# combinations
|
||||
self.assertEqual(
|
||||
InputCombination(
|
||||
InputCombination.from_tuples(
|
||||
(EV_KEY, BTN_A, 1),
|
||||
(EV_KEY, BTN_B, 1),
|
||||
(EV_KEY, BTN_C, 1),
|
||||
),
|
||||
).beautify(),
|
||||
"Button A + Button B + Button C",
|
||||
)
|
||||
|
||||
def test_find_analog_input_config(self):
|
||||
analog_input = InputConfig(type=EV_REL, code=REL_X)
|
||||
|
||||
combination = InputCombination(
|
||||
(
|
||||
InputConfig(type=EV_KEY, code=BTN_MIDDLE),
|
||||
InputConfig(type=EV_REL, code=REL_Y, analog_threshold=1),
|
||||
analog_input,
|
||||
)
|
||||
)
|
||||
self.assertIsNone(combination.find_analog_input_config(type_=EV_ABS))
|
||||
self.assertEqual(
|
||||
combination.find_analog_input_config(type_=EV_REL), analog_input
|
||||
)
|
||||
self.assertEqual(combination.find_analog_input_config(), analog_input)
|
||||
|
||||
combination = InputCombination(
|
||||
(
|
||||
InputConfig(type=EV_REL, code=REL_X, analog_threshold=1),
|
||||
InputConfig(type=EV_KEY, code=BTN_MIDDLE),
|
||||
)
|
||||
)
|
||||
self.assertIsNone(combination.find_analog_input_config(type_=EV_ABS))
|
||||
self.assertIsNone(combination.find_analog_input_config(type_=EV_REL))
|
||||
self.assertIsNone(combination.find_analog_input_config())
|
||||
|
||||
# region helper methods
|
||||
def assert_beautify_single(self, type_, code, direction, expected_beautified_name):
|
||||
"""
|
||||
Assert that the beautified name of the event tuple `(type_, code,
|
||||
direction)` matches the `expected_beautified_name`.
|
||||
"""
|
||||
self.assertEqual(
|
||||
InputCombination(
|
||||
InputCombination.from_tuples((type_, code, direction))
|
||||
).beautify(),
|
||||
expected_beautified_name,
|
||||
)
|
||||
|
||||
# endregion
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user