-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
6a195bf
commit 433178d
Showing
11 changed files
with
603 additions
and
53 deletions.
There are no files selected for viewing
Empty file.
Empty file.
149 changes: 149 additions & 0 deletions
149
midi_app_controller/actions/_tests/test_actions_handler.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,149 @@ | ||
from unittest.mock import Mock, call | ||
|
||
import pytest | ||
from app_model.types import Action | ||
|
||
from ..bound_controller import BoundController | ||
from ..actions_handler import ActionsHandler | ||
from midi_app_controller.models.binds import Binds | ||
from midi_app_controller.models.controller import Controller | ||
|
||
|
||
@pytest.fixture | ||
def bound_controller() -> BoundController: | ||
binds_data = { | ||
"name": "TestBinds", | ||
"app_name": "TestApp", | ||
"controller_name": "TestController", | ||
"button_binds": [{"button_id": 1, "action_id": "Action1"}], | ||
"knob_binds": [ | ||
{ | ||
"knob_id": 2, | ||
"action_id_increase": "incr", | ||
"action_id_decrease": "decr", | ||
} | ||
], | ||
} | ||
|
||
controller_data = { | ||
"name": "TestController", | ||
"button_value_off": 0, | ||
"button_value_on": 127, | ||
"knob_value_min": 0, | ||
"knob_value_max": 127, | ||
"buttons": [{"id": 0, "name": "Button1"}, {"id": 1, "name": "Button2"}], | ||
"knobs": [{"id": 2, "name": "Knob1"}, {"id": 3, "name": "Knob2"}], | ||
} | ||
|
||
actions = [ | ||
Action( | ||
id="Action1", | ||
title="sdfgsdfg", | ||
callback=lambda: None, | ||
), | ||
Action( | ||
id="incr", | ||
title="dfgssdfg", | ||
callback=lambda: None, | ||
), | ||
Action( | ||
id="decr", | ||
title="sdfdfg", | ||
callback=lambda: None, | ||
), | ||
Action( | ||
id="other", | ||
title="aaaasfd", | ||
callback=lambda: None, | ||
), | ||
] | ||
|
||
return BoundController.create( | ||
controller=Controller(**controller_data), | ||
binds=Binds(**binds_data), | ||
actions=actions, | ||
) | ||
|
||
|
||
def test_is_button_toggled(bound_controller): | ||
actions_handler = ActionsHandler(bound_controller=bound_controller, app=Mock()) | ||
|
||
with pytest.raises(NotImplementedError): | ||
actions_handler.is_button_toggled(0) | ||
|
||
|
||
def test_get_knob_value(bound_controller): | ||
actions_handler = ActionsHandler(bound_controller=bound_controller, app=Mock()) | ||
|
||
with pytest.raises(NotImplementedError): | ||
actions_handler.get_knob_value(0) | ||
|
||
|
||
def test_handle_button_action(bound_controller): | ||
app = Mock() | ||
actions_handler = ActionsHandler(bound_controller=bound_controller, app=app) | ||
|
||
actions_handler.handle_button_action(1) | ||
|
||
app.commands.execute_command.assert_called_once_with("Action1") | ||
|
||
|
||
@pytest.mark.parametrize("button_id", [3, 10, 1000]) | ||
def test_handle_button_action_when_button_not_bound(bound_controller, button_id): | ||
app = Mock() | ||
actions_handler = ActionsHandler(bound_controller=bound_controller, app=app) | ||
|
||
actions_handler.handle_button_action(button_id) | ||
|
||
app.commands.execute_command.assert_not_called() | ||
|
||
|
||
@pytest.mark.parametrize("old_value, new_value", [(10, 20), (10, 10), (10, 11)]) | ||
def test_handle_knob_action_increase(bound_controller, old_value, new_value): | ||
app = Mock() | ||
actions_handler = ActionsHandler(bound_controller=bound_controller, app=app) | ||
|
||
actions_handler.handle_knob_action( | ||
knob_id=2, old_value=old_value, new_value=new_value | ||
) | ||
|
||
assert old_value <= new_value | ||
calls = [call("incr")] * (new_value - old_value) | ||
app.commands.execute_command.assert_has_calls(calls) | ||
|
||
|
||
@pytest.mark.parametrize("old_value, new_value", [(20, 10), (11, 10)]) | ||
def test_handle_knob_action_decrease(bound_controller, old_value, new_value): | ||
app = Mock() | ||
actions_handler = ActionsHandler(bound_controller=bound_controller, app=app) | ||
|
||
actions_handler.handle_knob_action( | ||
knob_id=2, old_value=old_value, new_value=new_value | ||
) | ||
|
||
assert old_value > new_value | ||
calls = [call("decr")] * (old_value - new_value) | ||
app.commands.execute_command.assert_has_calls(calls) | ||
|
||
|
||
@pytest.mark.parametrize( | ||
"knob_id, old_value, new_value", | ||
[ | ||
(3, 10, 10), | ||
(3, 11, 10), | ||
(3, 10, 11), | ||
(1000, 10, 20), | ||
(-1, 10, 20), | ||
], | ||
) | ||
def test_handle_knob_action_when_knob_not_bound( | ||
bound_controller, knob_id, old_value, new_value | ||
): | ||
app = Mock() | ||
actions_handler = ActionsHandler(bound_controller=bound_controller, app=app) | ||
|
||
actions_handler.handle_knob_action( | ||
knob_id=knob_id, old_value=old_value, new_value=new_value | ||
) | ||
|
||
app.commands.execute_command.assert_not_called() |
162 changes: 162 additions & 0 deletions
162
midi_app_controller/actions/_tests/test_bound_controller.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,162 @@ | ||
import pytest | ||
from typing import List | ||
from app_model.types import Action | ||
|
||
from ..bound_controller import BoundController, ButtonActions, KnobActions | ||
from midi_app_controller.models.binds import Binds | ||
from midi_app_controller.models.controller import Controller | ||
|
||
|
||
@pytest.fixture | ||
def binds() -> Binds: | ||
binds_data = { | ||
"name": "TestBinds", | ||
"description": "Test description", | ||
"app_name": "TestApp", | ||
"controller_name": "TestController", | ||
"button_binds": [{"button_id": 1, "action_id": "Action1"}], | ||
"knob_binds": [ | ||
{ | ||
"knob_id": 2, | ||
"action_id_increase": "incr", | ||
"action_id_decrease": "decr", | ||
} | ||
], | ||
} | ||
return Binds(**binds_data) | ||
|
||
|
||
@pytest.fixture | ||
def controller() -> Controller: | ||
controller_data = { | ||
"name": "TestController", | ||
"button_value_off": 11, | ||
"button_value_on": 100, | ||
"knob_value_min": 33, | ||
"knob_value_max": 55, | ||
"buttons": [{"id": 0, "name": "Button1"}, {"id": 1, "name": "Button2"}], | ||
"knobs": [{"id": 2, "name": "Knob1"}, {"id": 3, "name": "Knob2"}], | ||
} | ||
return Controller(**controller_data) | ||
|
||
|
||
@pytest.fixture | ||
def actions() -> List[Action]: | ||
return [ | ||
Action( | ||
id="Action1", | ||
title="sdfgsdfg", | ||
callback=lambda: None, | ||
), | ||
Action( | ||
id="incr", | ||
title="dfgssdfg", | ||
callback=lambda: None, | ||
), | ||
Action( | ||
id="decr", | ||
title="sdfdfg", | ||
callback=lambda: None, | ||
), | ||
Action( | ||
id="other", | ||
title="aaaasfd", | ||
callback=lambda: None, | ||
), | ||
] | ||
|
||
|
||
@pytest.fixture | ||
def bound_controller(binds, controller, actions) -> BoundController: | ||
return BoundController.create(binds=binds, controller=controller, actions=actions) | ||
|
||
|
||
def test_button_actions(actions): | ||
data = {"action_press": actions[0]} | ||
actions = ButtonActions(**data) | ||
|
||
assert actions.dict() == data | ||
|
||
|
||
def test_knob_actions(actions): | ||
data = {"action_increase": actions[0], "action_decrease": actions[1]} | ||
actions = KnobActions(**data) | ||
|
||
assert actions.dict() == data | ||
|
||
|
||
def test_bound_controller(binds, controller, actions): | ||
bound_controller = BoundController.create( | ||
binds=binds, controller=controller, actions=actions | ||
) | ||
|
||
assert bound_controller.knob_value_min == controller.knob_value_min | ||
assert bound_controller.knob_value_max == controller.knob_value_max | ||
assert bound_controller.buttons.keys() == { | ||
bind.button_id for bind in binds.button_binds | ||
} | ||
assert bound_controller.knobs.keys() == {bind.knob_id for bind in binds.knob_binds} | ||
|
||
for bind in binds.button_binds: | ||
bound_button = bound_controller.buttons[bind.button_id] | ||
assert bound_button.action_press.id == bind.action_id | ||
for bind in binds.knob_binds: | ||
bound_knob = bound_controller.knobs[bind.knob_id] | ||
assert bound_knob.action_increase.id == bind.action_id_increase | ||
assert bound_knob.action_decrease.id == bind.action_id_decrease | ||
|
||
|
||
@pytest.mark.parametrize("action_index_to_delete", [0, 1, 2]) | ||
def test_non_existent_action_id(binds, controller, actions, action_index_to_delete): | ||
actions.pop(action_index_to_delete) | ||
|
||
with pytest.raises(ValueError): | ||
BoundController.create(binds=binds, controller=controller, actions=actions) | ||
|
||
|
||
def test_non_existent_knob(binds, controller, actions): | ||
controller.knobs.pop(0) | ||
|
||
with pytest.raises(ValueError): | ||
BoundController.create(binds=binds, controller=controller, actions=actions) | ||
|
||
|
||
def test_non_existent_button(binds, controller, actions): | ||
controller.buttons.pop(1) | ||
|
||
with pytest.raises(ValueError): | ||
BoundController.create(binds=binds, controller=controller, actions=actions) | ||
|
||
|
||
def test_different_controller_name(binds, controller, actions): | ||
controller.name = "asdfjkasdfjk" | ||
|
||
with pytest.raises(ValueError): | ||
BoundController.create(binds=binds, controller=controller, actions=actions) | ||
|
||
|
||
def test_get_button_press_action(actions, bound_controller): | ||
assert bound_controller.get_button_press_action(1) == actions[0] | ||
|
||
|
||
@pytest.mark.parametrize("knob_id", [2, 10, 1000]) | ||
def test_get_button_press_action_when_not_found(bound_controller, knob_id): | ||
assert bound_controller.get_button_press_action(knob_id) is None | ||
|
||
|
||
def test_get_knob_increase_action(actions, bound_controller): | ||
assert bound_controller.get_knob_increase_action(2) == actions[1] | ||
|
||
|
||
@pytest.mark.parametrize("knob_id", [3, 10, 1000]) | ||
def test_get_knob_increase_action_when_not_found(bound_controller, knob_id): | ||
assert bound_controller.get_knob_increase_action(knob_id) is None | ||
|
||
|
||
def test_get_knob_decrease_action(actions, bound_controller): | ||
assert bound_controller.get_knob_decrease_action(2) == actions[2] | ||
|
||
|
||
@pytest.mark.parametrize("knob_id", [3, 10, 1000]) | ||
def test_get_knob_decrease_action_when_not_found(bound_controller, knob_id): | ||
assert bound_controller.get_knob_decrease_action(knob_id) is None |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
from app_model import Application | ||
from typing import Optional | ||
|
||
from .bound_controller import BoundController | ||
|
||
|
||
class ActionsHandler: | ||
"""Allows to execute actions and get their state using ids of controller elements.""" | ||
|
||
def __init__( | ||
self, | ||
*, | ||
bound_controller: BoundController, | ||
app: Application, | ||
) -> None: | ||
"""Initializes the handler. | ||
Parameters | ||
---------- | ||
bound_controller : BoundController | ||
Controller and binds that should be handled. | ||
app : Application | ||
Application where the actions will be executed. | ||
""" | ||
self.bound_controller = bound_controller | ||
self.app = app | ||
|
||
def is_button_toggled(self, button_id: int) -> Optional[bool]: | ||
"""Checks if the action associated with the button is toggled.""" | ||
raise NotImplementedError # TODO | ||
|
||
def get_knob_value(self, knob_id: int) -> Optional[int]: | ||
"""Returns knob's value from the action associated with the knob.""" | ||
raise NotImplementedError # TODO | ||
|
||
def handle_button_action(self, button_id: int) -> None: | ||
"""Executes an action associated with the button if it exists.""" | ||
action = self.bound_controller.get_button_press_action(button_id) | ||
if action is not None: | ||
self.app.commands.execute_command(action.id) | ||
|
||
def handle_knob_action( | ||
self, | ||
*, | ||
knob_id: int, | ||
old_value: int, | ||
new_value: int, | ||
) -> None: | ||
"""Executes an action based on how the knob's value changed if it exists. | ||
Parameters | ||
---------- | ||
knob_id : int | ||
The id of the knob. | ||
old_value : int | ||
Value of the knob before it was rotated. | ||
new_value : int | ||
Received new value of the knob. | ||
""" | ||
diff = new_value - old_value | ||
if diff >= 0: | ||
action = self.bound_controller.get_knob_increase_action(knob_id) | ||
else: | ||
action = self.bound_controller.get_knob_decrease_action(knob_id) | ||
|
||
# TODO How many times should the command be executed? Maybe we | ||
# should let the user set it while binding the knob? | ||
# When fetching current value of actions will be added to app_model, | ||
# we could also execute the action once, check how much value | ||
# changed, and then adjust the sensitivity accordingly. | ||
if action is not None: | ||
for _ in range(abs(diff)): | ||
self.app.commands.execute_command(action.id) |
Oops, something went wrong.