summaryrefslogtreecommitdiffstats
path: root/tests/test_key_binding.py
diff options
context:
space:
mode:
Diffstat (limited to 'tests/test_key_binding.py')
-rw-r--r--tests/test_key_binding.py200
1 files changed, 200 insertions, 0 deletions
diff --git a/tests/test_key_binding.py b/tests/test_key_binding.py
new file mode 100644
index 0000000..1c60880
--- /dev/null
+++ b/tests/test_key_binding.py
@@ -0,0 +1,200 @@
+from __future__ import annotations
+
+from contextlib import contextmanager
+
+import pytest
+
+from prompt_toolkit.application import Application
+from prompt_toolkit.application.current import set_app
+from prompt_toolkit.input.defaults import create_pipe_input
+from prompt_toolkit.key_binding.key_bindings import KeyBindings
+from prompt_toolkit.key_binding.key_processor import KeyPress, KeyProcessor
+from prompt_toolkit.keys import Keys
+from prompt_toolkit.layout import Layout, Window
+from prompt_toolkit.output import DummyOutput
+
+
+class Handlers:
+ def __init__(self):
+ self.called = []
+
+ def __getattr__(self, name):
+ def func(event):
+ self.called.append(name)
+
+ return func
+
+
+@contextmanager
+def set_dummy_app():
+ """
+ Return a context manager that makes sure that this dummy application is
+ active. This is important, because we need an `Application` with
+ `is_done=False` flag, otherwise no keys will be processed.
+ """
+ with create_pipe_input() as pipe_input:
+ app = Application(
+ layout=Layout(Window()),
+ output=DummyOutput(),
+ input=pipe_input,
+ )
+
+ # Don't start background tasks for these tests. The `KeyProcessor`
+ # wants to create a background task for flushing keys. We can ignore it
+ # here for these tests.
+ # This patch is not clean. In the future, when we can use Taskgroups,
+ # the `Application` should pass its task group to the constructor of
+ # `KeyProcessor`. That way, it doesn't have to do a lookup using
+ # `get_app()`.
+ app.create_background_task = lambda *_, **kw: None
+
+ with set_app(app):
+ yield
+
+
+@pytest.fixture
+def handlers():
+ return Handlers()
+
+
+@pytest.fixture
+def bindings(handlers):
+ bindings = KeyBindings()
+ bindings.add(Keys.ControlX, Keys.ControlC)(handlers.controlx_controlc)
+ bindings.add(Keys.ControlX)(handlers.control_x)
+ bindings.add(Keys.ControlD)(handlers.control_d)
+ bindings.add(Keys.ControlSquareClose, Keys.Any)(handlers.control_square_close_any)
+
+ return bindings
+
+
+@pytest.fixture
+def processor(bindings):
+ return KeyProcessor(bindings)
+
+
+def test_remove_bindings(handlers):
+ with set_dummy_app():
+ h = handlers.controlx_controlc
+ h2 = handlers.controld
+
+ # Test passing a handler to the remove() function.
+ bindings = KeyBindings()
+ bindings.add(Keys.ControlX, Keys.ControlC)(h)
+ bindings.add(Keys.ControlD)(h2)
+ assert len(bindings.bindings) == 2
+ bindings.remove(h)
+ assert len(bindings.bindings) == 1
+
+ # Test passing a key sequence to the remove() function.
+ bindings = KeyBindings()
+ bindings.add(Keys.ControlX, Keys.ControlC)(h)
+ bindings.add(Keys.ControlD)(h2)
+ assert len(bindings.bindings) == 2
+ bindings.remove(Keys.ControlX, Keys.ControlC)
+ assert len(bindings.bindings) == 1
+
+
+def test_feed_simple(processor, handlers):
+ with set_dummy_app():
+ processor.feed(KeyPress(Keys.ControlX, "\x18"))
+ processor.feed(KeyPress(Keys.ControlC, "\x03"))
+ processor.process_keys()
+
+ assert handlers.called == ["controlx_controlc"]
+
+
+def test_feed_several(processor, handlers):
+ with set_dummy_app():
+ # First an unknown key first.
+ processor.feed(KeyPress(Keys.ControlQ, ""))
+ processor.process_keys()
+
+ assert handlers.called == []
+
+ # Followed by a know key sequence.
+ processor.feed(KeyPress(Keys.ControlX, ""))
+ processor.feed(KeyPress(Keys.ControlC, ""))
+ processor.process_keys()
+
+ assert handlers.called == ["controlx_controlc"]
+
+ # Followed by another unknown sequence.
+ processor.feed(KeyPress(Keys.ControlR, ""))
+ processor.feed(KeyPress(Keys.ControlS, ""))
+
+ # Followed again by a know key sequence.
+ processor.feed(KeyPress(Keys.ControlD, ""))
+ processor.process_keys()
+
+ assert handlers.called == ["controlx_controlc", "control_d"]
+
+
+def test_control_square_closed_any(processor, handlers):
+ with set_dummy_app():
+ processor.feed(KeyPress(Keys.ControlSquareClose, ""))
+ processor.feed(KeyPress("C", "C"))
+ processor.process_keys()
+
+ assert handlers.called == ["control_square_close_any"]
+
+
+def test_common_prefix(processor, handlers):
+ with set_dummy_app():
+ # Sending Control_X should not yet do anything, because there is
+ # another sequence starting with that as well.
+ processor.feed(KeyPress(Keys.ControlX, ""))
+ processor.process_keys()
+
+ assert handlers.called == []
+
+ # When another key is pressed, we know that we did not meant the longer
+ # "ControlX ControlC" sequence and the callbacks are called.
+ processor.feed(KeyPress(Keys.ControlD, ""))
+ processor.process_keys()
+
+ assert handlers.called == ["control_x", "control_d"]
+
+
+def test_previous_key_sequence(processor):
+ """
+ test whether we receive the correct previous_key_sequence.
+ """
+ with set_dummy_app():
+ events = []
+
+ def handler(event):
+ events.append(event)
+
+ # Build registry.
+ registry = KeyBindings()
+ registry.add("a", "a")(handler)
+ registry.add("b", "b")(handler)
+ processor = KeyProcessor(registry)
+
+ # Create processor and feed keys.
+ processor.feed(KeyPress("a", "a"))
+ processor.feed(KeyPress("a", "a"))
+ processor.feed(KeyPress("b", "b"))
+ processor.feed(KeyPress("b", "b"))
+ processor.process_keys()
+
+ # Test.
+ assert len(events) == 2
+ assert len(events[0].key_sequence) == 2
+ assert events[0].key_sequence[0].key == "a"
+ assert events[0].key_sequence[0].data == "a"
+ assert events[0].key_sequence[1].key == "a"
+ assert events[0].key_sequence[1].data == "a"
+ assert events[0].previous_key_sequence == []
+
+ assert len(events[1].key_sequence) == 2
+ assert events[1].key_sequence[0].key == "b"
+ assert events[1].key_sequence[0].data == "b"
+ assert events[1].key_sequence[1].key == "b"
+ assert events[1].key_sequence[1].data == "b"
+ assert len(events[1].previous_key_sequence) == 2
+ assert events[1].previous_key_sequence[0].key == "a"
+ assert events[1].previous_key_sequence[0].data == "a"
+ assert events[1].previous_key_sequence[1].key == "a"
+ assert events[1].previous_key_sequence[1].data == "a"