# Copyright 2014-2021 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # # qutebrowser is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # qutebrowser is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with qutebrowser. If not, see . import operator import hypothesis from hypothesis import strategies import pytest from qutebrowser.qt import machinery from qutebrowser.qt.core import Qt, QEvent, pyqtSignal from qutebrowser.qt.gui import QKeyEvent, QKeySequence from qutebrowser.qt.widgets import QWidget from helpers import testutils from unit.keyinput import key_data from qutebrowser.keyinput import keyutils from qutebrowser.utils import utils pyqt_enum_workaround_skip = pytest.mark.skipif( isinstance(keyutils._NIL_KEY, int), reason="Can't create QKey for unknown keys with this PyQt version" ) try: OE_KEY = Qt.Key(ord('Œ')) except ValueError: OE_KEY = None # affected tests skipped @pytest.fixture(params=key_data.KEYS, ids=lambda k: k.attribute) def qt_key(request): """Get all existing keys from key_data.py. Keys which don't exist with this Qt version result in skipped tests. """ key = request.param if key.member is None: pytest.skip("Did not find key {}".format(key.attribute)) return key @pytest.fixture(params=key_data.MODIFIERS, ids=lambda m: m.attribute) def qt_mod(request): """Get all existing modifiers from key_data.py.""" mod = request.param assert mod.member is not None return mod @pytest.fixture(params=[key for key in key_data.KEYS if key.qtest], ids=lambda k: k.attribute) def qtest_key(request): """Get keys from key_data.py which can be used with QTest.""" return request.param def test_key_data_keys(): """Make sure all possible keys are in key_data.KEYS.""" key_names = {name[len("Key_"):] for name in testutils.enum_members(Qt, Qt.Key)} key_data_names = {key.attribute for key in sorted(key_data.KEYS)} diff = key_names - key_data_names assert not diff def test_key_data_modifiers(): """Make sure all possible modifiers are in key_data.MODIFIERS.""" mod_names = {name[:-len("Modifier")] for name, value in testutils.enum_members(Qt, Qt.KeyboardModifier).items() if value not in [Qt.KeyboardModifier.NoModifier, Qt.KeyboardModifier.KeyboardModifierMask]} mod_data_names = {mod.attribute for mod in sorted(key_data.MODIFIERS)} diff = mod_names - mod_data_names assert not diff class KeyTesterWidget(QWidget): """Widget to get the text of QKeyPressEvents. This is done so we can check QTest::keyToAscii (qasciikey.cpp) as we can't call that directly, only via QTest::keyPress. """ got_text = pyqtSignal() def __init__(self, parent=None): super().__init__(parent) self.text = None def keyPressEvent(self, e): self.text = e.text() self.got_text.emit() class TestKeyInfoText: @pytest.mark.parametrize('upper', [False, True]) def test_text(self, qt_key, upper): """Test KeyInfo.text() with all possible keys. See key_data.py for inputs and expected values. """ modifiers = Qt.KeyboardModifier.ShiftModifier if upper else Qt.KeyboardModifier.NoModifier info = keyutils.KeyInfo(qt_key.member, modifiers=modifiers) expected = qt_key.uppertext if upper else qt_key.text assert info.text() == expected @pytest.fixture def key_tester(self, qtbot): w = KeyTesterWidget() qtbot.add_widget(w) return w def test_text_qtest(self, qtest_key, qtbot, key_tester): """Make sure KeyInfo.text() lines up with QTest::keyToAscii. See key_data.py for inputs and expected values. """ with qtbot.wait_signal(key_tester.got_text): qtbot.keyPress(key_tester, qtest_key.member) info = keyutils.KeyInfo(qtest_key.member) assert info.text() == key_tester.text.lower() class TestKeyToString: def test_to_string(self, qt_key): assert keyutils._key_to_string(qt_key.member) == qt_key.name def test_modifiers_to_string(self, qt_mod): expected = qt_mod.name + '+' assert keyutils._modifiers_to_string(qt_mod.member) == expected @pytest.mark.skipif(machinery.IS_QT6, reason="Can't delete enum members on PyQt 6") def test_missing(self, monkeypatch): monkeypatch.delattr(keyutils.Qt, 'Key_AltGr') # We don't want to test the key which is actually missing - we only # want to know if the mapping still behaves properly. assert keyutils._key_to_string(Qt.Key.Key_A) == 'A' @pytest.mark.parametrize('key, modifiers, expected', [ (Qt.Key.Key_A, Qt.KeyboardModifier.NoModifier, 'a'), (Qt.Key.Key_A, Qt.KeyboardModifier.ShiftModifier, 'A'), (Qt.Key.Key_Space, Qt.KeyboardModifier.NoModifier, ''), (Qt.Key.Key_Space, Qt.KeyboardModifier.ShiftModifier, ''), (Qt.Key.Key_Tab, Qt.KeyboardModifier.ShiftModifier, ''), (Qt.Key.Key_A, Qt.KeyboardModifier.ControlModifier, ''), (Qt.Key.Key_A, Qt.KeyboardModifier.ControlModifier | Qt.KeyboardModifier.ShiftModifier, ''), (Qt.Key.Key_A, Qt.KeyboardModifier.ControlModifier | Qt.KeyboardModifier.AltModifier | Qt.KeyboardModifier.MetaModifier | Qt.KeyboardModifier.ShiftModifier, ''), pytest.param(OE_KEY, Qt.KeyboardModifier.NoModifier, '<Œ>', marks=pyqt_enum_workaround_skip), pytest.param(OE_KEY, Qt.KeyboardModifier.ShiftModifier, '', marks=pyqt_enum_workaround_skip), pytest.param(OE_KEY, Qt.KeyboardModifier.GroupSwitchModifier, '', marks=pyqt_enum_workaround_skip), pytest.param(OE_KEY, Qt.KeyboardModifier.GroupSwitchModifier | Qt.KeyboardModifier.ShiftModifier, ''), (Qt.Key.Key_Shift, Qt.KeyboardModifier.ShiftModifier, ''), (Qt.Key.Key_Shift, Qt.KeyboardModifier.ShiftModifier | Qt.KeyboardModifier.ControlModifier, ''), (Qt.Key.Key_Alt, Qt.KeyboardModifier.AltModifier, ''), (Qt.Key.Key_Shift, Qt.KeyboardModifier.GroupSwitchModifier | Qt.KeyboardModifier.ShiftModifier, ''), (Qt.Key.Key_AltGr, Qt.KeyboardModifier.GroupSwitchModifier, ''), ]) def test_key_info_str(key, modifiers, expected): assert str(keyutils.KeyInfo(key, modifiers)) == expected def test_key_info_repr(): info = keyutils.KeyInfo(Qt.Key.Key_A, Qt.KeyboardModifier.ShiftModifier) expected = ( "") assert repr(info) == expected @pytest.mark.parametrize('info1, info2, equal', [ (keyutils.KeyInfo(Qt.Key.Key_A, Qt.KeyboardModifier.NoModifier), keyutils.KeyInfo(Qt.Key.Key_A, Qt.KeyboardModifier.NoModifier), True), (keyutils.KeyInfo(Qt.Key.Key_A, Qt.KeyboardModifier.NoModifier), keyutils.KeyInfo(Qt.Key.Key_B, Qt.KeyboardModifier.NoModifier), False), (keyutils.KeyInfo(Qt.Key.Key_A, Qt.KeyboardModifier.NoModifier), keyutils.KeyInfo(Qt.Key.Key_B, Qt.KeyboardModifier.ControlModifier), False), ]) def test_hash(info1, info2, equal): assert (hash(info1) == hash(info2)) == equal @pytest.mark.parametrize('key, modifiers, text, expected', [ (0xd83c, Qt.KeyboardModifier.NoModifier, '🏻', '<🏻>'), (0xd867, Qt.KeyboardModifier.NoModifier, '𩷶', '<𩷶>'), (0xd867, Qt.KeyboardModifier.ShiftModifier, '𩷶', ''), ]) def test_surrogates(key, modifiers, text, expected, pyqt_enum_workaround): evt = QKeyEvent(QEvent.Type.KeyPress, key, modifiers, text) with pyqt_enum_workaround(): info = keyutils.KeyInfo.from_event(evt) assert str(info) == expected @pytest.mark.parametrize('keys, expected', [ ([0x1f3fb], '<🏻>'), ([0x29df6], '<𩷶>'), ([Qt.Key.Key_Shift, 0x29df6], '<𩷶>'), ([0x1f468, 0x200d, 0x1f468, 0x200d, 0x1f466], '<👨><‍><👨><‍><👦>'), ]) @pyqt_enum_workaround_skip def test_surrogate_sequences(keys, expected): infos = [keyutils.KeyInfo(Qt.Key(key)) for key in keys] seq = keyutils.KeySequence(*infos) assert str(seq) == expected # This shouldn't happen, but if it does we should handle it well def test_surrogate_error(pyqt_enum_workaround): evt = QKeyEvent(QEvent.Type.KeyPress, 0xd83e, Qt.KeyboardModifier.NoModifier, '🤞🏻') with pytest.raises(keyutils.KeyParseError), pyqt_enum_workaround(): keyutils.KeyInfo.from_event(evt) @pytest.mark.parametrize('keystr, expected', [ ('foo', "Could not parse 'foo': error"), (None, "Could not parse keystring: error"), ]) def test_key_parse_error(keystr, expected): exc = keyutils.KeyParseError(keystr, "error") assert str(exc) == expected @pytest.mark.parametrize('keystr, parts', [ ('a', ['a']), ('ab', ['a', 'b']), ('a<', ['a', '<']), ('a>', ['a', '>']), ('a', ['>', 'a']), ('aA', ['a', 'Shift+A']), ('ab', ['a', 'ctrl+a', 'b']), ('a', ['ctrl+a', 'a']), ('a', ['a', 'ctrl+a']), ('', ['ctrl+a']), ('', ['num+a']), ]) def test_parse_keystr(keystr, parts): assert list(keyutils._parse_keystring(keystr)) == parts class TestKeySequence: def test_init(self): seq = keyutils.KeySequence( keyutils.KeyInfo(Qt.Key.Key_A), keyutils.KeyInfo(Qt.Key.Key_B), keyutils.KeyInfo(Qt.Key.Key_C), keyutils.KeyInfo(Qt.Key.Key_D), keyutils.KeyInfo(Qt.Key.Key_E), ) assert len(seq._sequences) == 2 assert len(seq._sequences[0]) == 4 assert len(seq._sequences[1]) == 1 def test_init_empty(self): seq = keyutils.KeySequence() assert not seq @pytest.mark.parametrize('key', [Qt.Key.Key_unknown, keyutils._NIL_KEY]) def test_init_unknown(self, key): with pytest.raises(keyutils.KeyParseError): keyutils.KeySequence(keyutils.KeyInfo(key)) def test_init_invalid(self): with pytest.raises(AssertionError): keyutils.KeyInfo(-1) def test_parse_unknown(self): with pytest.raises(keyutils.KeyParseError): keyutils.KeySequence.parse('\x1f') @pytest.mark.parametrize('orig, normalized', [ ('', ''), ('', ''), ('', ''), ('', ''), ('', ''), ('', ''), ('', ''), ('', ''), ('', ''), ('', ''), ('', ''), ('b', 'ab'), ]) def test_str_normalization(self, orig, normalized): assert str(keyutils.KeySequence.parse(orig)) == normalized def test_iter(self): infos = [keyutils.KeyInfo(Qt.Key.Key_A, Qt.KeyboardModifier.ControlModifier), keyutils.KeyInfo(Qt.Key.Key_B, Qt.KeyboardModifier.ShiftModifier), keyutils.KeyInfo(Qt.Key.Key_C), keyutils.KeyInfo(Qt.Key.Key_D), keyutils.KeyInfo(Qt.Key.Key_E)] seq = keyutils.KeySequence(*infos) assert list(seq) == infos def test_repr(self): seq = keyutils.KeySequence(keyutils.KeyInfo(Qt.Key.Key_A, Qt.KeyboardModifier.ControlModifier), keyutils.KeyInfo(Qt.Key.Key_B, Qt.KeyboardModifier.ShiftModifier)) assert repr(seq) == ("") @pytest.mark.parametrize('sequences, expected', [ (['a', ''], ['', 'a']), (['abcdf', 'abcd', 'abcde'], ['abcd', 'abcde', 'abcdf']), ]) def test_sorting(self, sequences, expected): result = sorted(keyutils.KeySequence.parse(seq) for seq in sequences) expected_result = [keyutils.KeySequence.parse(seq) for seq in expected] assert result == expected_result @pytest.mark.parametrize('seq1, seq2, op, result', [ ('a', 'a', operator.eq, True), ('a', '', operator.eq, True), ('a', '', operator.eq, False), ('a', 'b', operator.lt, True), ('a', 'b', operator.le, True), ]) def test_operators(self, seq1, seq2, op, result): seq1 = keyutils.KeySequence.parse(seq1) seq2 = keyutils.KeySequence.parse(seq2) assert op(seq1, seq2) == result opposite = { operator.lt: operator.ge, operator.gt: operator.le, operator.le: operator.gt, operator.ge: operator.lt, operator.eq: operator.ne, operator.ne: operator.eq, } assert opposite[op](seq1, seq2) != result @pytest.mark.parametrize('op, result', [ (operator.eq, False), (operator.ne, True), ]) def test_operators_other_type(self, op, result): seq = keyutils.KeySequence.parse('a') assert op(seq, 'x') == result @pytest.mark.parametrize('seq1, seq2, equal', [ ('a', 'a', True), ('a', 'A', False), ('a', '', True), ('abcd', 'abcde', False), ]) def test_hash(self, seq1, seq2, equal): seq1 = keyutils.KeySequence.parse(seq1) seq2 = keyutils.KeySequence.parse(seq2) assert (hash(seq1) == hash(seq2)) == equal @pytest.mark.parametrize('seq, length', [ ('', 0), ('a', 1), ('A', 1), ('', 1), ('abcde', 5) ]) def test_len(self, seq, length): assert len(keyutils.KeySequence.parse(seq)) == length def test_bool(self): seq1 = keyutils.KeySequence.parse('abcd') seq2 = keyutils.KeySequence() assert seq1 assert not seq2 def test_getitem(self): seq = keyutils.KeySequence.parse('ab') expected = keyutils.KeyInfo(Qt.Key.Key_B, Qt.KeyboardModifier.NoModifier) assert seq[1] == expected def test_getitem_slice(self): s1 = 'abcdef' s2 = 'de' seq = keyutils.KeySequence.parse(s1) expected = keyutils.KeySequence.parse(s2) assert s1[3:5] == s2 assert seq[3:5] == expected MATCH_TESTS = [ # config: abcd ('abc', 'abcd', QKeySequence.SequenceMatch.PartialMatch), ('abcd', 'abcd', QKeySequence.SequenceMatch.ExactMatch), ('ax', 'abcd', QKeySequence.SequenceMatch.NoMatch), ('abcdef', 'abcd', QKeySequence.SequenceMatch.NoMatch), # config: abcd ef ('abc', 'abcdef', QKeySequence.SequenceMatch.PartialMatch), ('abcde', 'abcdef', QKeySequence.SequenceMatch.PartialMatch), ('abcd', 'abcdef', QKeySequence.SequenceMatch.PartialMatch), ('abcdx', 'abcdef', QKeySequence.SequenceMatch.NoMatch), ('ax', 'abcdef', QKeySequence.SequenceMatch.NoMatch), ('abcdefg', 'abcdef', QKeySequence.SequenceMatch.NoMatch), ('abcdef', 'abcdef', QKeySequence.SequenceMatch.ExactMatch), # other examples ('ab', 'a', QKeySequence.SequenceMatch.NoMatch), # empty strings ('', '', QKeySequence.SequenceMatch.ExactMatch), ('', 'a', QKeySequence.SequenceMatch.PartialMatch), ('a', '', QKeySequence.SequenceMatch.NoMatch)] @pytest.mark.parametrize('entered, configured, match_type', MATCH_TESTS) def test_matches(self, entered, configured, match_type): entered = keyutils.KeySequence.parse(entered) configured = keyutils.KeySequence.parse(configured) assert entered.matches(configured) == match_type @pytest.mark.parametrize('old, key, modifiers, text, expected', [ ('a', Qt.Key.Key_B, Qt.KeyboardModifier.NoModifier, 'b', 'ab'), ('a', Qt.Key.Key_B, Qt.KeyboardModifier.ShiftModifier, 'B', 'aB'), ('a', Qt.Key.Key_B, Qt.KeyboardModifier.AltModifier | Qt.KeyboardModifier.ShiftModifier, 'B', 'a'), # Modifier stripping with symbols ('', Qt.Key.Key_Colon, Qt.KeyboardModifier.NoModifier, ':', ':'), ('', Qt.Key.Key_Colon, Qt.KeyboardModifier.ShiftModifier, ':', ':'), ('', Qt.Key.Key_Colon, Qt.KeyboardModifier.AltModifier | Qt.KeyboardModifier.ShiftModifier, ':', ''), # Swapping Control/Meta on macOS ('', Qt.Key.Key_A, Qt.KeyboardModifier.ControlModifier, '', '' if utils.is_mac else ''), ('', Qt.Key.Key_A, Qt.KeyboardModifier.ControlModifier | Qt.KeyboardModifier.ShiftModifier, '', '' if utils.is_mac else ''), ('', Qt.Key.Key_A, Qt.KeyboardModifier.MetaModifier, '', '' if utils.is_mac else ''), # Handling of Backtab ('', Qt.Key.Key_Backtab, Qt.KeyboardModifier.NoModifier, '', ''), ('', Qt.Key.Key_Backtab, Qt.KeyboardModifier.ShiftModifier, '', ''), ('', Qt.Key.Key_Backtab, Qt.KeyboardModifier.AltModifier | Qt.KeyboardModifier.ShiftModifier, '', ''), # Stripping of Qt.KeyboardModifier.GroupSwitchModifier ('', Qt.Key.Key_A, Qt.KeyboardModifier.GroupSwitchModifier, 'a', 'a'), ]) def test_append_event(self, old, key, modifiers, text, expected): seq = keyutils.KeySequence.parse(old) event = QKeyEvent(QEvent.Type.KeyPress, key, modifiers, text) new = seq.append_event(event) assert new == keyutils.KeySequence.parse(expected) @pytest.mark.fake_os('mac') @pytest.mark.parametrize('modifiers, expected', [ (Qt.KeyboardModifier.ControlModifier, Qt.KeyboardModifier.MetaModifier), (Qt.KeyboardModifier.MetaModifier, Qt.KeyboardModifier.ControlModifier), (Qt.KeyboardModifier.ControlModifier | Qt.KeyboardModifier.MetaModifier, Qt.KeyboardModifier.ControlModifier | Qt.KeyboardModifier.MetaModifier), (Qt.KeyboardModifier.ControlModifier | Qt.KeyboardModifier.ShiftModifier, Qt.KeyboardModifier.MetaModifier | Qt.KeyboardModifier.ShiftModifier), (Qt.KeyboardModifier.MetaModifier | Qt.KeyboardModifier.ShiftModifier, Qt.KeyboardModifier.ControlModifier | Qt.KeyboardModifier.ShiftModifier), (Qt.KeyboardModifier.ShiftModifier, Qt.KeyboardModifier.ShiftModifier), ]) def test_fake_mac(self, modifiers, expected): """Make sure Control/Meta are swapped with a simulated Mac.""" seq = keyutils.KeySequence() info = keyutils.KeyInfo(key=Qt.Key.Key_A, modifiers=modifiers) new = seq.append_event(info.to_event()) assert new[0] == keyutils.KeyInfo(Qt.Key.Key_A, expected) @pytest.mark.parametrize('key', [Qt.Key.Key_unknown, 0x0]) def test_append_event_invalid(self, key): seq = keyutils.KeySequence() event = QKeyEvent(QEvent.Type.KeyPress, key, Qt.KeyboardModifier.NoModifier, '') with pytest.raises(keyutils.KeyParseError): seq.append_event(event) def test_strip_modifiers(self): seq = keyutils.KeySequence(keyutils.KeyInfo(Qt.Key.Key_0), keyutils.KeyInfo(Qt.Key.Key_1, Qt.KeyboardModifier.KeypadModifier), keyutils.KeyInfo(Qt.Key.Key_A, Qt.KeyboardModifier.ControlModifier)) expected = keyutils.KeySequence(keyutils.KeyInfo(Qt.Key.Key_0), keyutils.KeyInfo(Qt.Key.Key_1), keyutils.KeyInfo(Qt.Key.Key_A, Qt.KeyboardModifier.ControlModifier)) assert seq.strip_modifiers() == expected @pytest.mark.parametrize('inp, mappings, expected', [ ('foobar', {'b': 't'}, 'footar'), ('foobar', {'': ''}, 'foobar'), ('foobar', {'b': 'sa'}, 'foosaar'), ]) def test_with_mappings(self, inp, mappings, expected): seq = keyutils.KeySequence.parse(inp) seq2 = seq.with_mappings({ keyutils.KeySequence.parse(k): keyutils.KeySequence.parse(v) for k, v in mappings.items() }) assert seq2 == keyutils.KeySequence.parse(expected) @pytest.mark.parametrize('keystr, expected', [ ('', keyutils.KeySequence(keyutils.KeyInfo(Qt.Key.Key_Y, Qt.KeyboardModifier.ControlModifier | Qt.KeyboardModifier.AltModifier))), ('x', keyutils.KeySequence(keyutils.KeyInfo(Qt.Key.Key_X))), ('X', keyutils.KeySequence(keyutils.KeyInfo(Qt.Key.Key_X, Qt.KeyboardModifier.ShiftModifier))), ('', keyutils.KeySequence(keyutils.KeyInfo(Qt.Key.Key_Escape))), ('xyz', keyutils.KeySequence(keyutils.KeyInfo(Qt.Key.Key_X), keyutils.KeyInfo(Qt.Key.Key_Y), keyutils.KeyInfo(Qt.Key.Key_Z))), ('', keyutils.KeySequence(keyutils.KeyInfo(Qt.Key.Key_X, Qt.KeyboardModifier.ControlModifier), keyutils.KeyInfo(Qt.Key.Key_Y, Qt.KeyboardModifier.MetaModifier))), ('', keyutils.KeySequence(keyutils.KeyInfo(Qt.Key.Key_X, Qt.KeyboardModifier.ShiftModifier))), ('', keyutils.KeySequence(keyutils.KeyInfo(Qt.Key.Key_X, Qt.KeyboardModifier.AltModifier))), ('', keyutils.KeySequence(keyutils.KeyInfo(Qt.Key.Key_X, Qt.KeyboardModifier.ControlModifier))), ('', keyutils.KeySequence(keyutils.KeyInfo(Qt.Key.Key_X, Qt.KeyboardModifier.MetaModifier))), ('', keyutils.KeySequence(keyutils.KeyInfo(Qt.Key.Key_X, Qt.KeyboardModifier.KeypadModifier))), ('>', keyutils.KeySequence(keyutils.KeyInfo(Qt.Key.Key_Greater))), ('<', keyutils.KeySequence(keyutils.KeyInfo(Qt.Key.Key_Less))), ('a>', keyutils.KeySequence(keyutils.KeyInfo(Qt.Key.Key_A), keyutils.KeyInfo(Qt.Key.Key_Greater))), ('a<', keyutils.KeySequence(keyutils.KeyInfo(Qt.Key.Key_A), keyutils.KeyInfo(Qt.Key.Key_Less))), ('>a', keyutils.KeySequence(keyutils.KeyInfo(Qt.Key.Key_Greater), keyutils.KeyInfo(Qt.Key.Key_A))), ('', keyutils.KeySequence(keyutils.KeyInfo(Qt.Key.Key_Greater, Qt.KeyboardModifier.AltModifier))), ('', keyutils.KeySequence(keyutils.KeyInfo(Qt.Key.Key_Less, Qt.KeyboardModifier.AltModifier))), ('', keyutils.KeyParseError), ('>', keyutils.KeyParseError), ('', keyutils.KeyParseError), ('<>', keyutils.KeyParseError), ('\U00010000', keyutils.KeyParseError), ]) def test_parse(self, keystr, expected): if expected is keyutils.KeyParseError: with pytest.raises(keyutils.KeyParseError): keyutils.KeySequence.parse(keystr) else: assert keyutils.KeySequence.parse(keystr) == expected @hypothesis.given(strategies.text()) def test_parse_hypothesis(self, keystr): try: seq = keyutils.KeySequence.parse(keystr) except keyutils.KeyParseError: pass else: str(seq) def test_key_info_from_event(): ev = QKeyEvent(QEvent.Type.KeyPress, Qt.Key.Key_A, Qt.KeyboardModifier.ShiftModifier, 'A') info = keyutils.KeyInfo.from_event(ev) assert info.key == Qt.Key.Key_A assert info.modifiers == Qt.KeyboardModifier.ShiftModifier def test_key_info_to_event(): info = keyutils.KeyInfo(Qt.Key.Key_A, Qt.KeyboardModifier.ShiftModifier) ev = info.to_event() assert ev.key() == Qt.Key.Key_A assert ev.modifiers() == Qt.KeyboardModifier.ShiftModifier assert ev.text() == 'A' def test_key_info_to_qt(): info = keyutils.KeyInfo(Qt.Key.Key_A, Qt.KeyboardModifier.ShiftModifier) assert info.to_qt() == Qt.Key.Key_A | Qt.KeyboardModifier.ShiftModifier @pytest.mark.parametrize('key, printable', [ (Qt.Key.Key_Control, False), (Qt.Key.Key_Escape, False), (Qt.Key.Key_Tab, False), (Qt.Key.Key_Backtab, False), (Qt.Key.Key_Backspace, False), (Qt.Key.Key_Return, False), (Qt.Key.Key_Enter, False), (Qt.Key.Key_Space, False), # Used by Qt for unknown keys pytest.param(keyutils._NIL_KEY, False, marks=pyqt_enum_workaround_skip), (Qt.Key.Key_ydiaeresis, True), (Qt.Key.Key_X, True), ]) def test_is_printable(key, printable): assert keyutils._is_printable(key) == printable info = keyutils.KeyInfo(key, Qt.KeyboardModifier.NoModifier) assert info.is_special() != printable @pytest.mark.parametrize('key, modifiers, special', [ (Qt.Key.Key_Escape, Qt.KeyboardModifier.NoModifier, True), (Qt.Key.Key_Escape, Qt.KeyboardModifier.ShiftModifier, True), (Qt.Key.Key_Escape, Qt.KeyboardModifier.ControlModifier, True), (Qt.Key.Key_X, Qt.KeyboardModifier.ControlModifier, True), (Qt.Key.Key_X, Qt.KeyboardModifier.NoModifier, False), (Qt.Key.Key_2, Qt.KeyboardModifier.KeypadModifier, True), (Qt.Key.Key_2, Qt.KeyboardModifier.NoModifier, False), (Qt.Key.Key_Shift, Qt.KeyboardModifier.ShiftModifier, True), (Qt.Key.Key_Control, Qt.KeyboardModifier.ControlModifier, True), (Qt.Key.Key_Alt, Qt.KeyboardModifier.AltModifier, True), (Qt.Key.Key_Meta, Qt.KeyboardModifier.MetaModifier, True), (Qt.Key.Key_Mode_switch, Qt.KeyboardModifier.GroupSwitchModifier, True), ]) def test_is_special(key, modifiers, special): assert keyutils.KeyInfo(key, modifiers).is_special() == special @pytest.mark.parametrize('key, ismodifier', [ (Qt.Key.Key_Control, True), (Qt.Key.Key_X, False), (Qt.Key.Key_Super_L, False), # Modifier but not in _MODIFIER_MAP ]) def test_is_modifier_key(key, ismodifier): assert keyutils.KeyInfo(key).is_modifier_key() == ismodifier @pytest.mark.parametrize('func', [ keyutils._assert_plain_key, keyutils._assert_plain_modifier, keyutils._is_printable, keyutils._key_to_string, keyutils._modifiers_to_string, keyutils.KeyInfo, ]) def test_non_plain(func): comb = Qt.Key.Key_X | Qt.KeyboardModifier.ControlModifier if machinery.IS_QT6: # QKeyCombination comb = comb.toCombined() with pytest.raises(AssertionError): func(comb)