From 31b999ea59ed1c861fb7532539639c03579fa554 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sun, 2 Jul 2017 22:10:28 +0200 Subject: [PATCH] Tests and improvements for KeyConfig --- qutebrowser/config/config.py | 33 ++++---- tests/unit/config/test_config.py | 129 +++++++++++++++++++++++++++---- 2 files changed, 131 insertions(+), 31 deletions(-) diff --git a/qutebrowser/config/config.py b/qutebrowser/config/config.py index f1e6a5038..7a7171b33 100644 --- a/qutebrowser/config/config.py +++ b/qutebrowser/config/config.py @@ -131,6 +131,15 @@ class KeyConfig: def __init__(self, config): self._config = config + def _prepare(self, key, mode): + """Make sure the given mode exists and normalize the key.""" + if mode not in configdata.DATA['bindings.default'].default: + raise configexc.KeybindingError("Invalid mode {}!".format(mode)) + if utils.is_special_key(key): + # , , and should be considered equivalent + return utils.normalize_keystr(key) + return key + def get_bindings_for(self, mode): """Get the combined bindings for the given mode.""" bindings = dict(val.bindings.default[mode]) @@ -156,14 +165,11 @@ class KeyConfig: cmd_to_keys[cmd].insert(0, key) return cmd_to_keys - def _prepare(self, key, mode): - """Make sure the given mode exists and normalize the key.""" - if mode not in configdata.DATA['bindings.default'].default: - raise configexc.KeybindingError("Invalid mode {}!".format(mode)) - if utils.is_special_key(key): - # , , and should be considered equivalent - return utils.normalize_keystr(key) - return key + def get_command(self, key, mode): + """Get the command for a given key (or None).""" + key = self._prepare(key, mode) + bindings = self.get_bindings_for(mode) + return bindings.get(key, None) def bind(self, key, command, *, mode, force=False, save_yaml=False): """Add a new binding from key to command.""" @@ -175,7 +181,7 @@ class KeyConfig: except cmdexc.Error as e: raise configexc.KeybindingError("Invalid command: {}".format(e)) - for result in results: + for result in results: # pragma: no branch try: result.cmd.validate_mode(usertypes.KeyMode[mode]) except cmdexc.PrerequisitesError as e: @@ -207,16 +213,11 @@ class KeyConfig: bindings_commands[mode] = {} bindings_commands[mode][key] = None else: - raise configexc.KeybindingError("Can't find binding '{}' in section '{}'!" - .format(key, mode)) + raise configexc.KeybindingError( + "Can't find binding '{}' in {} mode".format(key, mode)) self._config.update_mutables(save_yaml=save_yaml) - def get_command(self, key, mode): - """Get the command for a given key (or None).""" - key = self._prepare(key, mode) - return val.bindings.commands[mode].get(key, None) - class ConfigCommands: diff --git a/tests/unit/config/test_config.py b/tests/unit/config/test_config.py index 89e130345..21302df6e 100644 --- a/tests/unit/config/test_config.py +++ b/tests/unit/config/test_config.py @@ -89,6 +89,24 @@ class TestKeyConfig: config_stub.val.aliases = {} return config.KeyConfig(config_stub) + @pytest.fixture + def no_bindings(self): + """Get a dict with no bindings.""" + return {'normal': {}} + + @pytest.mark.parametrize('key, expected', [ + ('A', 'A'), + ('', ''), + ]) + def test_prepare_valid(self, keyconf, key, expected): + """Make sure prepare normalizes the key.""" + assert keyconf._prepare(key, 'normal') == expected + + def test_prepare_invalid(self, keyconf): + """Make sure prepare checks the mode.""" + with pytest.raises(configexc.KeybindingError): + assert keyconf._prepare('x', 'abnormal') + @pytest.mark.parametrize('commands, expected', [ # Unbinding default key ({'a': None}, {'b': 'message-info bar'}), @@ -98,7 +116,8 @@ class TestKeyConfig: # Unbinding unknown key ({'x': None}, {'a': 'message-info foo', 'b': 'message-info bar'}), ]) - def test_get_bindings_for(self, keyconf, config_stub, commands, expected): + def test_get_bindings_for_and_get_command(self, keyconf, config_stub, + commands, expected): orig_default_bindings = {'normal': {'a': 'message-info foo', 'b': 'message-info bar'}, 'insert': {}, @@ -115,6 +134,13 @@ class TestKeyConfig: # Make sure the code creates a copy and doesn't modify the setting assert config_stub.val.bindings.default == orig_default_bindings assert bindings == expected + for key, command in expected.items(): + assert keyconf.get_command(key, 'normal') == command + + def test_get_command_unbound(self, keyconf, config_stub, no_bindings): + config_stub.val.bindings.default = no_bindings + config_stub.val.bindings.commands = no_bindings + assert keyconf.get_command('foobar', 'normal') is None @pytest.mark.parametrize('bindings, expected', [ # Simple @@ -130,24 +156,97 @@ class TestKeyConfig: ({'a': 'message-info foo ;; message-info bar'}, {'message-info foo': ['a'], 'message-info bar': ['a']}), ]) - def test_get_reverse_bindings_for(self, keyconf, config_stub, bindings, - expected): - config_stub.val.bindings.default = {'normal': {}} + def test_get_reverse_bindings_for(self, keyconf, config_stub, no_bindings, + bindings, expected): + config_stub.val.bindings.default = no_bindings config_stub.val.bindings.commands = {'normal': bindings} assert keyconf.get_reverse_bindings_for('normal') == expected - @pytest.mark.parametrize('key, expected', [ - ('A', 'A'), - ('', ''), - ]) - def test_prepare_valid(self, keyconf, key, expected): - """Make sure prepare normalizes the key.""" - assert keyconf._prepare(key, 'normal') == expected + def test_bind_invalid_command(self, keyconf): + with pytest.raises(configexc.KeybindingError, + match='Invalid command: foobar'): + keyconf.bind('a', 'foobar', mode='normal') - def test_prepare_invalid(self, keyconf): - """Make sure prepare checks the mode.""" - with pytest.raises(configexc.KeybindingError): - assert keyconf._prepare('x', 'abnormal') + def test_bind_invalid_mode(self, keyconf): + with pytest.raises(configexc.KeybindingError, + match='completion-item-del: This command is only ' + 'allowed in command mode, not normal.'): + keyconf.bind('a', 'completion-item-del', mode='normal') + + @pytest.mark.parametrize('as_default', [True, False]) + @pytest.mark.parametrize('force', [True, False]) + @pytest.mark.parametrize('key', ['a', '']) + def test_bind_duplicate(self, keyconf, config_stub, no_bindings, + as_default, force, key): + bindings = {'normal': {'a': 'nop', '': 'nop'}} + if as_default: + config_stub.val.bindings.default = bindings + config_stub.val.bindings.commands = no_bindings + else: + config_stub.val.bindings.default = no_bindings + config_stub.val.bindings.commands = bindings + + if force: + keyconf.bind(key, 'message-info foo', mode='normal', force=True) + assert keyconf.get_command(key, 'normal') == 'message-info foo' + else: + with pytest.raises(configexc.DuplicateKeyError): + keyconf.bind(key, 'message-info foo', mode='normal') + assert keyconf.get_command(key, 'normal') == 'nop' + + @pytest.mark.parametrize('mode', ['normal', 'caret']) + def test_bind(self, keyconf, config_stub, qtbot, no_bindings, mode): + config_stub.val.bindings.default = no_bindings + config_stub.val.bindings.commands = no_bindings + + command = 'message-info foo' + + with qtbot.wait_signal(config_stub.changed): + keyconf.bind('a', command, mode=mode) + + assert config_stub.val.bindings.commands[mode]['a'] == command + assert keyconf.get_bindings_for(mode)['a'] == command + assert keyconf.get_command('a', mode) == command + + @pytest.mark.parametrize('as_default', [True, False]) + @pytest.mark.parametrize('key, normalized', [ + ('a', 'a'), + ('', '') + ]) + @pytest.mark.parametrize('mode', ['normal', 'caret']) + def test_unbind(self, keyconf, config_stub, qtbot, no_bindings, + as_default, key, normalized, mode): + bindings = { + 'normal': {'a': 'nop', '': 'nop'}, + 'caret': {'a': 'nop', '': 'nop'}, + } + if as_default: + config_stub.val.bindings.default = bindings + config_stub.val.bindings.commands = no_bindings + else: + config_stub.val.bindings.default = no_bindings + config_stub.val.bindings.commands = bindings + + with qtbot.wait_signal(config_stub.changed): + keyconf.unbind(key, mode=mode) + + assert keyconf.get_command(key, mode) is None + + mode_bindings = config_stub.val.bindings.commands[mode] + if as_default: + default_bindings = config_stub.val.bindings.default + assert default_bindings[mode] == bindings[mode] + assert mode_bindings[normalized] is None + else: + assert normalized not in mode_bindings + + def test_unbind_unbound(self, keyconf, config_stub, no_bindings): + """Try unbinding a key which is not bound.""" + config_stub.val.bindings.default = no_bindings + config_stub.val.bindings.commands = no_bindings + with pytest.raises(configexc.KeybindingError, + match="Can't find binding 'foobar' in normal mode"): + keyconf.unbind('foobar', mode='normal') class StyleObj(QObject):