1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 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"
|