From 46cfbddad2244393629c06551c1b14f18798cc8c Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Wed, 21 Dec 2022 14:57:25 +0000 Subject: [PATCH] check for empty bindings --- src/textual/binding.py | 10 +++++++- tests/test_binding.py | 56 +++++++++++++++++++++++++++++++++--------- 2 files changed, 54 insertions(+), 12 deletions(-) diff --git a/src/textual/binding.py b/src/textual/binding.py index 353158218..927434868 100644 --- a/src/textual/binding.py +++ b/src/textual/binding.py @@ -19,6 +19,10 @@ class NoBinding(Exception): """A binding was not found.""" +class InvalidBinding(Exception): + """Binding key is in an invalid format.""" + + @dataclass(frozen=True) class Binding: """The configuration of a key binding.""" @@ -71,10 +75,14 @@ class Bindings: # into a (potential) collection of Binding instances. for key in binding.key.split(","): key = key.strip() + if not key: + raise InvalidBinding( + f"Can not bind empty string in {binding.key!r}" + ) if len(key) == 1: key = _character_to_key(key) yield Binding( - key=key.strip(), + key=key, action=binding.action, description=binding.description, show=binding.show, diff --git a/tests/test_binding.py b/tests/test_binding.py index 3c12f5a7c..9a24b3e2d 100644 --- a/tests/test_binding.py +++ b/tests/test_binding.py @@ -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")]