diff --git a/tests/helpers/fixtures.py b/tests/helpers/fixtures.py index 193a40a8a..d30514f83 100644 --- a/tests/helpers/fixtures.py +++ b/tests/helpers/fixtures.py @@ -35,8 +35,7 @@ import types import attr import pytest import py.path # pylint: disable=no-name-in-module -from PyQt5.QtCore import QEvent, QSize, Qt -from PyQt5.QtGui import QKeyEvent +from PyQt5.QtCore import QSize, Qt from PyQt5.QtWidgets import QWidget, QHBoxLayout, QVBoxLayout from PyQt5.QtNetwork import QNetworkCookieJar @@ -354,21 +353,6 @@ def webframe(webpage): return webpage.mainFrame() -@pytest.fixture -def fake_keyevent_factory(): - """Fixture that when called will return a mock instance of a QKeyEvent.""" - def fake_keyevent(key, modifiers=0, text='', typ=QEvent.KeyPress): - """Generate a new fake QKeyPressEvent.""" - evtmock = unittest.mock.create_autospec(QKeyEvent, instance=True) - evtmock.key.return_value = key - evtmock.modifiers.return_value = modifiers - evtmock.text.return_value = text - evtmock.type.return_value = typ - return evtmock - - return fake_keyevent - - @pytest.fixture def cookiejar_and_cache(stubs): """Fixture providing a fake cookie jar and cache.""" diff --git a/tests/unit/keyinput/conftest.py b/tests/unit/keyinput/conftest.py index f78ed61ad..96d07f744 100644 --- a/tests/unit/keyinput/conftest.py +++ b/tests/unit/keyinput/conftest.py @@ -21,6 +21,11 @@ import pytest +from PyQt5.QtCore import QEvent, Qt +from PyQt5.QtGui import QKeyEvent + +from qutebrowser.keyinput import keyutils + BINDINGS = {'prompt': {'': 'message-info ctrla', 'a': 'message-info a', @@ -33,8 +38,6 @@ BINDINGS = {'prompt': {'': 'message-info ctrla', '': 'message-info ctrlx'}, 'normal': {'a': 'message-info a', 'ba': 'message-info ba'}} MAPPINGS = { - '': 'a', - '': '', 'x': 'a', 'b': 'a', } @@ -46,3 +49,14 @@ def keyinput_bindings(config_stub, key_config_stub): config_stub.val.bindings.default = {} config_stub.val.bindings.commands = dict(BINDINGS) config_stub.val.bindings.key_mappings = dict(MAPPINGS) + + +@pytest.fixture +def fake_keyevent(): + """Fixture that when called will return a mock instance of a QKeyEvent.""" + def func(key, modifiers=Qt.NoModifier, typ=QEvent.KeyPress): + """Generate a new fake QKeyPressEvent.""" + text = keyutils.KeyInfo(key, modifiers).text() + return QKeyEvent(QKeyEvent.KeyPress, key, modifiers, text) + + return func diff --git a/tests/unit/keyinput/test_basekeyparser.py b/tests/unit/keyinput/test_basekeyparser.py index 872709d6e..95860399d 100644 --- a/tests/unit/keyinput/test_basekeyparser.py +++ b/tests/unit/keyinput/test_basekeyparser.py @@ -42,15 +42,15 @@ def keyparser(key_config_stub): @pytest.fixture -def handle_text(fake_keyevent_factory, keyparser): +def handle_text(fake_keyevent, keyparser): """Helper function to handle multiple fake keypresses. Automatically uses the keyparser of the current test via the keyparser fixture. """ def func(*args): - for enumval, text in args: - keyparser.handle(fake_keyevent_factory(enumval, text=text)) + for enumval in args: + keyparser.handle(fake_keyevent(enumval)) return func @@ -76,6 +76,7 @@ class TestDebugLog: ('10g', True, '10', 'g'), ('10e4g', True, '4', 'g'), ('g', True, '', 'g'), + ('0', True, '', ''), ('10g', False, '', 'g'), ]) def test_split_count(config_stub, key_config_stub, @@ -140,115 +141,65 @@ class TestReadConfig: assert (keyseq('new') in keyparser.bindings) == expected -class TestSpecialKeys: - - """Check execute() with special keys.""" +class TestHandle: @pytest.fixture(autouse=True) def read_config(self, keyinput_bindings, keyparser): keyparser._read_config('prompt') - def test_valid_key(self, fake_keyevent_factory, keyparser): + def test_valid_key(self, fake_keyevent, keyparser): modifier = Qt.MetaModifier if utils.is_mac else Qt.ControlModifier - keyparser.handle(fake_keyevent_factory(Qt.Key_A, modifier)) - keyparser.handle(fake_keyevent_factory(Qt.Key_X, modifier)) - keyparser.execute.assert_called_once_with('message-info ctrla', None) - - def test_valid_key_count(self, fake_keyevent_factory, keyparser): - modifier = Qt.MetaModifier if utils.is_mac else Qt.ControlModifier - keyparser.handle(fake_keyevent_factory(Qt.Key_5, text='5')) - keyparser.handle(fake_keyevent_factory(Qt.Key_A, modifier, text='A')) - keyparser.execute.assert_called_once_with('message-info ctrla', 5) - - def test_invalid_key(self, fake_keyevent_factory, keyparser): - keyparser.handle(fake_keyevent_factory( - Qt.Key_A, (Qt.ControlModifier | Qt.AltModifier))) - assert not keyparser.execute.called - - def test_only_modifiers(self, monkeypatch, fake_keyevent_factory, - keyparser): - monkeypatch.setattr(keyutils.KeyInfo, '__str__', lambda _self: '') - keyparser.handle(fake_keyevent_factory(Qt.Key_Shift, Qt.NoModifier)) - assert not keyparser.execute.called - - def test_mapping(self, config_stub, fake_keyevent_factory, keyparser): - modifier = Qt.MetaModifier if utils.is_mac else Qt.ControlModifier - - keyparser.handle(fake_keyevent_factory(Qt.Key_B, modifier)) - keyparser.execute.assert_called_once_with('message-info ctrla', None) - - def test_binding_and_mapping(self, config_stub, fake_keyevent_factory, - keyparser): - """with a conflicting binding/mapping, the binding should win.""" - modifier = Qt.MetaModifier if utils.is_mac else Qt.ControlModifier - - keyparser.handle(fake_keyevent_factory(Qt.Key_A, modifier)) - keyparser.execute.assert_called_once_with('message-info ctrla', None) - - -class TestKeyChain: - - """Test execute() with keychain support.""" - - @pytest.fixture(autouse=True) - def read_config(self, keyinput_bindings, keyparser): - keyparser._read_config('prompt') - - def test_valid_special_key(self, fake_keyevent_factory, keyparser): - if utils.is_mac: - modifier = Qt.MetaModifier - else: - modifier = Qt.ControlModifier - keyparser.handle(fake_keyevent_factory(Qt.Key_A, modifier)) - keyparser.handle(fake_keyevent_factory(Qt.Key_X, modifier)) + keyparser.handle(fake_keyevent(Qt.Key_A, modifier)) + keyparser.handle(fake_keyevent(Qt.Key_X, modifier)) keyparser.execute.assert_called_once_with('message-info ctrla', None) assert not keyparser._sequence - def test_invalid_special_key(self, fake_keyevent_factory, keyparser): - keyparser.handle(fake_keyevent_factory( - Qt.Key_A, (Qt.ControlModifier | Qt.AltModifier))) + def test_valid_key_count(self, fake_keyevent, keyparser): + modifier = Qt.MetaModifier if utils.is_mac else Qt.ControlModifier + keyparser.handle(fake_keyevent(Qt.Key_5)) + keyparser.handle(fake_keyevent(Qt.Key_A, modifier)) + keyparser.execute.assert_called_once_with('message-info ctrla', 5) + + @pytest.mark.parametrize('keys', [ + [(Qt.Key_B, Qt.NoModifier), (Qt.Key_C, Qt.NoModifier)], + [(Qt.Key_A, Qt.ControlModifier | Qt.AltModifier)], + # Only modifier + [(Qt.Key_Shift, Qt.ShiftModifier)], + ]) + def test_invalid_keys(self, fake_keyevent, keyparser, keys): + for key, modifiers in keys: + keyparser.handle(fake_keyevent(key, modifiers)) assert not keyparser.execute.called assert not keyparser._sequence def test_valid_keychain(self, handle_text, keyparser): # Press 'x' which is ignored because of no match - handle_text((Qt.Key_X, 'x'), + handle_text(Qt.Key_X, # Then start the real chain - (Qt.Key_B, 'b'), (Qt.Key_A, 'a')) + Qt.Key_B, Qt.Key_A) keyparser.execute.assert_called_with('message-info ba', None) assert not keyparser._sequence def test_0_press(self, handle_text, keyparser): - handle_text((Qt.Key_0, '0')) + handle_text(Qt.Key_0) keyparser.execute.assert_called_once_with('message-info 0', None) assert not keyparser._sequence - def test_ambiguous_keychain(self, handle_text, keyparser): - handle_text((Qt.Key_A, 'a')) - assert keyparser.execute.called - - def test_invalid_keychain(self, handle_text, keyparser): - handle_text((Qt.Key_B, 'b')) - handle_text((Qt.Key_C, 'c')) - assert not keyparser._sequence - def test_mapping(self, config_stub, handle_text, keyparser): - handle_text((Qt.Key_X, 'x')) + handle_text(Qt.Key_X) keyparser.execute.assert_called_once_with('message-info a', None) def test_binding_and_mapping(self, config_stub, handle_text, keyparser): """with a conflicting binding/mapping, the binding should win.""" - handle_text((Qt.Key_B, 'b')) + handle_text(Qt.Key_B) assert not keyparser.execute.called - def test_binding_with_shift(self, keyparser, fake_keyevent_factory): + def test_binding_with_shift(self, keyparser, fake_keyevent): """Simulate a binding which involves shift.""" - keyparser.handle( - fake_keyevent_factory(Qt.Key_Y, text='y')) - keyparser.handle( - fake_keyevent_factory(Qt.Key_Shift, Qt.ShiftModifier, text='')) - keyparser.handle( - fake_keyevent_factory(Qt.Key_Y, Qt.ShiftModifier, text='Y')) + for key, modifiers in [(Qt.Key_Y, Qt.NoModifier), + (Qt.Key_Shift, Qt.ShiftModifier), + (Qt.Key_Y, Qt.ShiftModifier)]: + keyparser.handle(fake_keyevent(key, modifiers)) keyparser.execute.assert_called_once_with('yank -s', None) @@ -263,32 +214,29 @@ class TestCount: def test_no_count(self, handle_text, keyparser): """Test with no count added.""" - handle_text((Qt.Key_B, 'b'), (Qt.Key_A, 'a')) + handle_text(Qt.Key_B, Qt.Key_A) keyparser.execute.assert_called_once_with('message-info ba', None) assert not keyparser._sequence def test_count_0(self, handle_text, keyparser): - handle_text((Qt.Key_0, '0'), (Qt.Key_B, 'b'), (Qt.Key_A, 'a')) + handle_text(Qt.Key_0, Qt.Key_B, Qt.Key_A) calls = [mock.call('message-info 0', None), mock.call('message-info ba', None)] keyparser.execute.assert_has_calls(calls) assert not keyparser._sequence def test_count_42(self, handle_text, keyparser): - handle_text((Qt.Key_4, '4'), (Qt.Key_2, '2'), (Qt.Key_B, 'b'), - (Qt.Key_A, 'a')) + handle_text(Qt.Key_4, Qt.Key_2, Qt.Key_B, Qt.Key_A) keyparser.execute.assert_called_once_with('message-info ba', 42) assert not keyparser._sequence def test_count_42_invalid(self, handle_text, keyparser): # Invalid call with ccx gets ignored - handle_text((Qt.Key_4, '4'), (Qt.Key_2, '2'), (Qt.Key_C, 'c'), - (Qt.Key_C, 'c'), (Qt.Key_X, 'x')) + handle_text(Qt.Key_4, Qt.Key_2, Qt.Key_C, Qt.Key_C, Qt.Key_X) assert not keyparser.execute.called assert not keyparser._sequence # Valid call with ccc gets the correct count - handle_text((Qt.Key_2, '2'), (Qt.Key_3, '3'), (Qt.Key_C, 'c'), - (Qt.Key_C, 'c'), (Qt.Key_C, 'c')) + handle_text(Qt.Key_2, Qt.Key_3, Qt.Key_C, Qt.Key_C, Qt.Key_C) keyparser.execute.assert_called_once_with('message-info ccc', 23) assert not keyparser._sequence @@ -296,6 +244,15 @@ class TestCount: def test_clear_keystring(qtbot, keyparser): """Test that the keystring is cleared and the signal is emitted.""" keyparser._sequence = keyseq('test') + keyparser._count = '23' with qtbot.waitSignal(keyparser.keystring_updated): keyparser.clear_keystring() assert not keyparser._sequence + assert not keyparser._count + + +def test_clear_keystring_empty(qtbot, keyparser): + """Test that no signal is emitted when clearing an empty keystring..""" + keyparser._sequence = keyseq('') + with qtbot.assert_not_emitted(keyparser.keystring_updated): + keyparser.clear_keystring() diff --git a/tests/unit/keyinput/test_modeman.py b/tests/unit/keyinput/test_modeman.py index 221b675be..de9671961 100644 --- a/tests/unit/keyinput/test_modeman.py +++ b/tests/unit/keyinput/test_modeman.py @@ -44,15 +44,14 @@ def modeman(mode_manager): return mode_manager -@pytest.mark.parametrize('key, modifiers, text, filtered', [ - (Qt.Key_A, Qt.NoModifier, 'a', True), - (Qt.Key_Up, Qt.NoModifier, '', False), +@pytest.mark.parametrize('key, modifiers, filtered', [ + (Qt.Key_A, Qt.NoModifier, True), + (Qt.Key_Up, Qt.NoModifier, False), # https://github.com/qutebrowser/qutebrowser/issues/1207 - (Qt.Key_A, Qt.ShiftModifier, 'A', True), - (Qt.Key_A, Qt.ShiftModifier | Qt.ControlModifier, 'x', False), + (Qt.Key_A, Qt.ShiftModifier, True), + (Qt.Key_A, Qt.ShiftModifier | Qt.ControlModifier, False), ]) -def test_non_alphanumeric(key, modifiers, text, filtered, - fake_keyevent_factory, modeman): +def test_non_alphanumeric(key, modifiers, filtered, fake_keyevent, modeman): """Make sure non-alphanumeric keys are passed through correctly.""" - evt = fake_keyevent_factory(key=key, modifiers=modifiers, text=text) + evt = fake_keyevent(key=key, modifiers=modifiers) assert modeman.eventFilter(evt) == filtered diff --git a/tests/unit/keyinput/test_modeparsers.py b/tests/unit/keyinput/test_modeparsers.py index d53328b7e..c3be9d70f 100644 --- a/tests/unit/keyinput/test_modeparsers.py +++ b/tests/unit/keyinput/test_modeparsers.py @@ -49,25 +49,25 @@ class TestsNormalKeyParser: kp.execute = mock.Mock() return kp - def test_keychain(self, keyparser, fake_keyevent_factory): + def test_keychain(self, keyparser, fake_keyevent): """Test valid keychain.""" # Press 'x' which is ignored because of no match - keyparser.handle(fake_keyevent_factory(Qt.Key_X, text='x')) + keyparser.handle(fake_keyevent(Qt.Key_X)) # Then start the real chain - keyparser.handle(fake_keyevent_factory(Qt.Key_B, text='b')) - keyparser.handle(fake_keyevent_factory(Qt.Key_A, text='a')) + keyparser.handle(fake_keyevent(Qt.Key_B)) + keyparser.handle(fake_keyevent(Qt.Key_A)) keyparser.execute.assert_called_with('message-info ba', None) assert not keyparser._sequence def test_partial_keychain_timeout(self, keyparser, config_stub, - fake_keyevent_factory): + fake_keyevent): """Test partial keychain timeout.""" config_stub.val.input.partial_timeout = 100 timer = keyparser._partial_timer assert not timer.isActive() # Press 'b' for a partial match. # Then we check if the timer has been set up correctly - keyparser.handle(fake_keyevent_factory(Qt.Key_B, text='b')) + keyparser.handle(fake_keyevent(Qt.Key_B)) assert timer.isSingleShot() assert timer.interval() == 100 assert timer.isActive()