Merge pull request #1417 from Textualize/key-refactor

keys refactor
This commit is contained in:
Will McGugan
2022-12-21 15:54:34 +00:00
committed by GitHub
21 changed files with 261 additions and 148 deletions

View File

@@ -191,10 +191,9 @@ def pytest_terminal_summary(
console = Console(legacy_windows=False, force_terminal=True)
if diffs:
snapshot_report_location = config._textual_snapshot_html_report
console.rule("[b red]Textual Snapshot Report", style="red")
console.print("[b red]Textual Snapshot Report", style="red")
console.print(
f"\n[black on red]{len(diffs)} mismatched snapshots[/]\n"
f"\n[b]View the [link=file://{snapshot_report_location}]failure report[/].\n"
)
console.print(f"[dim]{snapshot_report_location}\n")
console.rule(style="red")

View File

@@ -2,7 +2,8 @@ from string import ascii_lowercase
import pytest
from textual.binding import Bindings, Binding, BindingError, NoBinding
from textual.app import App
from textual.binding import Bindings, Binding, BindingError, NoBinding, InvalidBinding
BINDING1 = Binding("a,b", action="action1", description="description1")
BINDING2 = Binding("c", action="action2", description="description2")
@@ -13,45 +14,78 @@ BINDING3 = Binding(" d , e ", action="action3", description="description3")
def bindings():
yield Bindings([BINDING1, BINDING2])
@pytest.fixture
def more_bindings():
yield Bindings([BINDING1, BINDING2, BINDING3])
def test_bindings_get_key(bindings):
assert bindings.get_key("b") == Binding("b", action="action1", description="description1")
assert bindings.get_key("b") == Binding(
"b", action="action1", description="description1"
)
assert bindings.get_key("c") == BINDING2
with pytest.raises(NoBinding):
bindings.get_key("control+meta+alt+shift+super+hyper+t")
def test_bindings_get_key_spaced_list(more_bindings):
assert more_bindings.get_key("d").action == more_bindings.get_key("e").action
def test_bindings_merge_simple(bindings):
left = Bindings([BINDING1])
right = Bindings([BINDING2])
assert Bindings.merge([left, right]).keys == bindings.keys
def test_bindings_merge_overlap():
left = Bindings([BINDING1])
another_binding = Binding("a", action="another_action", description="another_description")
another_binding = Binding(
"a", action="another_action", description="another_description"
)
assert Bindings.merge([left, Bindings([another_binding])]).keys == {
"a": another_binding,
"b": Binding("b", action="action1", description="description1"),
}
def test_bad_binding_tuple():
with pytest.raises(BindingError):
_ = Bindings((("a", "action"),))
with pytest.raises(BindingError):
_ = Bindings((("a", "action", "description","too much"),))
_ = Bindings((("a", "action", "description", "too much"),))
def test_binding_from_tuples():
assert Bindings((( BINDING2.key, BINDING2.action, BINDING2.description),)).get_key("c") == BINDING2
assert (
Bindings(((BINDING2.key, BINDING2.action, BINDING2.description),)).get_key("c")
== BINDING2
)
def test_shown():
bindings = Bindings([
Binding(
key, action=f"action_{key}", description=f"Emits {key}",show=bool(ord(key)%2)
) for key in ascii_lowercase
])
assert len(bindings.shown_keys)==(len(ascii_lowercase)/2)
bindings = Bindings(
[
Binding(
key,
action=f"action_{key}",
description=f"Emits {key}",
show=bool(ord(key) % 2),
)
for key in ascii_lowercase
]
)
assert len(bindings.shown_keys) == (len(ascii_lowercase) / 2)
def test_invalid_binding():
with pytest.raises(InvalidBinding):
class BrokenApp(App):
BINDINGS = [(",,,", "foo", "Broken")]
with pytest.raises(InvalidBinding):
class BrokenApp(App):
BINDINGS = [(", ,", "foo", "Broken")]

View File

@@ -57,7 +57,7 @@ async def test_just_app_no_bindings() -> None:
class AlphaBinding(App[None]):
"""An app with a simple alpha key binding."""
BINDINGS = [Binding("a", "a", "a")]
BINDINGS = [Binding("a", "a", "a", priority=True)]
async def test_just_app_alpha_binding() -> None:
@@ -81,8 +81,7 @@ async def test_just_app_alpha_binding() -> None:
class LowAlphaBinding(App[None]):
"""An app with a simple low-priority alpha key binding."""
PRIORITY_BINDINGS = False
BINDINGS = [Binding("a", "a", "a")]
BINDINGS = [Binding("a", "a", "a", priority=False)]
async def test_just_app_low_priority_alpha_binding() -> None:
@@ -106,7 +105,7 @@ async def test_just_app_low_priority_alpha_binding() -> None:
class ScreenWithBindings(Screen):
"""A screen with a simple alpha key binding."""
BINDINGS = [Binding("a", "a", "a")]
BINDINGS = [Binding("a", "a", "a", priority=True)]
class AppWithScreenThatHasABinding(App[None]):
@@ -144,8 +143,7 @@ async def test_app_screen_with_bindings() -> None:
class ScreenWithLowBindings(Screen):
"""A screen with a simple low-priority alpha key binding."""
PRIORITY_BINDINGS = False
BINDINGS = [Binding("a", "a", "a")]
BINDINGS = [Binding("a", "a", "a", priority=False)]
class AppWithScreenThatHasALowBinding(App[None]):

50
tests/test_keys.py Normal file
View File

@@ -0,0 +1,50 @@
import pytest
from textual.app import App
from textual.keys import _character_to_key
@pytest.mark.parametrize(
"character,key",
[
("1", "1"),
("2", "2"),
("a", "a"),
("z", "z"),
("_", "underscore"),
(" ", "space"),
("~", "tilde"),
("?", "question_mark"),
("£", "pound_sign"),
(",", "comma"),
],
)
def test_character_to_key(character: str, key: str) -> None:
assert _character_to_key(character) == key
async def test_character_bindings():
"""Test you can bind to a character as well as a longer key name."""
counter = 0
class BindApp(App):
BINDINGS = [(".,~,space", "increment", "foo")]
def action_increment(self) -> None:
nonlocal counter
counter += 1
app = BindApp()
async with app.run_test() as pilot:
await pilot.press(".")
await pilot.pause()
assert counter == 1
await pilot.press("~")
await pilot.pause()
assert counter == 2
await pilot.press(" ")
await pilot.pause()
assert counter == 3
await pilot.press("x")
await pilot.pause()
assert counter == 3

View File

@@ -17,7 +17,7 @@ class ValidWidget(Widget):
async def test_dispatch_key_valid_key():
widget = ValidWidget()
result = await widget.dispatch_key(Key(widget, key="x", char="x"))
result = await widget.dispatch_key(Key(widget, key="x", character="x"))
assert result is True
assert widget.called_by == widget.key_x
@@ -26,7 +26,7 @@ async def test_dispatch_key_valid_key_alias():
"""When you press tab or ctrl+i, it comes through as a tab key event, but handlers for
tab and ctrl+i are both considered valid."""
widget = ValidWidget()
result = await widget.dispatch_key(Key(widget, key="tab", char="\t"))
result = await widget.dispatch_key(Key(widget, key="tab", character="\t"))
assert result is True
assert widget.called_by == widget.key_ctrl_i
@@ -52,5 +52,5 @@ async def test_dispatch_key_raises_when_conflicting_handler_aliases():
In the terminal, they're the same thing, so we fail fast via exception here."""
widget = DuplicateHandlersWidget()
with pytest.raises(DuplicateKeyHandlers):
await widget.dispatch_key(Key(widget, key="tab", char="\t"))
await widget.dispatch_key(Key(widget, key="tab", character="\t"))
assert widget.called_by == widget.key_tab

View File

@@ -106,7 +106,7 @@ def test_cant_match_escape_sequence_too_long(parser):
# The rest of the characters correspond to the expected key presses
events = events[1:]
for index, character in enumerate(sequence[1:]):
assert events[index].char == character
assert events[index].character == character
@pytest.mark.parametrize(