1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
|
# Copyright 2015-2021 Florian Bruhin (The Compiler) <mail@qutebrowser.org>:
#
# 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 <https://www.gnu.org/licenses/>.
"""Tests for mode parsers."""
from qutebrowser.qt.core import Qt
from qutebrowser.qt.gui import QKeySequence
import pytest
from qutebrowser.keyinput import modeparsers, keyutils
from qutebrowser.config import configexc
@pytest.fixture
def commandrunner(stubs):
return stubs.FakeCommandRunner()
class TestsNormalKeyParser:
@pytest.fixture(autouse=True)
def patch_stuff(self, monkeypatch, stubs, keyinput_bindings):
"""Set up mocks and read the test config."""
monkeypatch.setattr(
'qutebrowser.keyinput.basekeyparser.usertypes.Timer',
stubs.FakeTimer)
@pytest.fixture
def keyparser(self, commandrunner):
kp = modeparsers.NormalKeyParser(win_id=0, commandrunner=commandrunner)
return kp
def test_keychain(self, keyparser, commandrunner):
"""Test valid keychain."""
# Press 'z' which is ignored because of no match
# Then start the real chain
chain = keyutils.KeySequence.parse('zba')
for info in chain:
keyparser.handle(info.to_event())
assert commandrunner.commands == [('message-info ba', None)]
assert not keyparser._sequence
def test_partial_keychain_timeout(self, keyparser, config_stub,
qtbot, commandrunner):
"""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(keyutils.KeyInfo(Qt.Key.Key_B, Qt.KeyboardModifier.NoModifier).to_event())
assert timer.isSingleShot()
assert timer.interval() == 100
assert timer.isActive()
assert not commandrunner.commands
assert keyparser._sequence == keyutils.KeySequence.parse('b')
# Now simulate a timeout and check the keystring has been cleared.
with qtbot.wait_signal(keyparser.keystring_updated) as blocker:
timer.timeout.emit()
assert not commandrunner.commands
assert not keyparser._sequence
assert blocker.args == ['']
class TestHintKeyParser:
@pytest.fixture
def hintmanager(self, stubs):
return stubs.FakeHintManager()
@pytest.fixture
def keyparser(self, config_stub, key_config_stub, commandrunner,
hintmanager):
return modeparsers.HintKeyParser(win_id=0,
hintmanager=hintmanager,
commandrunner=commandrunner)
@pytest.mark.parametrize('bindings, keychain, prefix, hint', [
(
['aa', 'as'],
'as',
'a',
'as'
),
(
['21', '22'],
'<Num+2><Num+2>',
'2',
'22'
),
(
['äa', 'äs'],
'äs',
'ä',
'äs'
),
(
['не', 'на'],
'не',
'<Н>',
'не',
),
])
def test_match(self, keyparser, hintmanager,
bindings, keychain, prefix, hint, pyqt_enum_workaround):
with pyqt_enum_workaround(keyutils.KeyParseError):
keyparser.update_bindings(bindings)
seq = keyutils.KeySequence.parse(keychain)
assert len(seq) == 2
match = keyparser.handle(seq[0].to_event())
assert match == QKeySequence.SequenceMatch.PartialMatch
assert hintmanager.keystr == prefix
match = keyparser.handle(seq[1].to_event())
assert match == QKeySequence.SequenceMatch.ExactMatch
assert hintmanager.keystr == hint
def test_match_key_mappings(self, config_stub, keyparser, hintmanager,
pyqt_enum_workaround):
with pyqt_enum_workaround(configexc.ValidationError):
config_stub.val.bindings.key_mappings = {'α': 'a', 'σ': 's'}
keyparser.update_bindings(['aa', 'as'])
seq = keyutils.KeySequence.parse('ασ')
assert len(seq) == 2
match = keyparser.handle(seq[0].to_event())
assert match == QKeySequence.SequenceMatch.PartialMatch
assert hintmanager.keystr == 'a'
match = keyparser.handle(seq[1].to_event())
assert match == QKeySequence.SequenceMatch.ExactMatch
assert hintmanager.keystr == 'as'
def test_command(self, keyparser, config_stub, hintmanager, commandrunner):
config_stub.val.bindings.commands = {
'hint': {'abc': 'message-info abc'}
}
keyparser.update_bindings(['xabcy'])
steps = [
(Qt.Key.Key_X, QKeySequence.SequenceMatch.PartialMatch, 'x'),
(Qt.Key.Key_A, QKeySequence.SequenceMatch.PartialMatch, ''),
(Qt.Key.Key_B, QKeySequence.SequenceMatch.PartialMatch, ''),
(Qt.Key.Key_C, QKeySequence.SequenceMatch.ExactMatch, ''),
]
for key, expected_match, keystr in steps:
info = keyutils.KeyInfo(key, Qt.KeyboardModifier.NoModifier)
match = keyparser.handle(info.to_event())
assert match == expected_match
assert hintmanager.keystr == keystr
if key != Qt.Key.Key_C:
assert not commandrunner.commands
assert commandrunner.commands == [('message-info abc', None)]
|