# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: # Copyright 2016-2021 Ryan Roden-Corrent (rcorre) # # 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 . """Test the keyhint widget.""" import pytest from qutebrowser.utils import usertypes from qutebrowser.misc import objects from qutebrowser.misc.keyhintwidget import KeyHintView def expected_text(*args): """Helper to format text we expect the KeyHintView to generate. Args: *args: One tuple for each row in the expected output. Tuples are of the form: (prefix, color, suffix, command). """ text = '' for group in args: text += ("" "" "" "" "").format(*group) return text + '
{}{}{}
' @pytest.fixture def keyhint(qtbot, config_stub, key_config_stub): """Fixture to initialize a KeyHintView.""" config_stub.val.colors.keyhint.suffix.fg = 'yellow' keyhint = KeyHintView(0, None) qtbot.add_widget(keyhint) assert keyhint.text() == '' return keyhint def test_show_and_hide(qtbot, keyhint): with qtbot.wait_signal(keyhint.update_geometry): with qtbot.wait_exposed(keyhint): keyhint.show() keyhint.update_keyhint(usertypes.KeyMode.normal, '') assert not keyhint.isVisible() def test_position_change(keyhint, config_stub): config_stub.val.statusbar.position = 'top' stylesheet = keyhint.styleSheet() assert 'border-bottom-right-radius' in stylesheet assert 'border-top-right-radius' not in stylesheet def test_suggestions(keyhint, config_stub): """Test that keyhints are shown based on a prefix.""" bindings = {'normal': { 'aa': 'message-info cmd-aa', 'ab': 'message-info cmd-ab', 'aba': 'message-info cmd-aba', 'abb': 'message-info cmd-abb', 'xd': 'message-info cmd-xd', 'xe': 'message-info cmd-xe', }} default_bindings = {'normal': { 'ac': 'message-info cmd-ac', }} config_stub.val.bindings.default = default_bindings config_stub.val.bindings.commands = bindings keyhint.update_keyhint(usertypes.KeyMode.normal, 'a') assert keyhint.text() == expected_text( ('a', 'yellow', 'a', 'message-info cmd-aa'), ('a', 'yellow', 'b', 'message-info cmd-ab'), ('a', 'yellow', 'ba', 'message-info cmd-aba'), ('a', 'yellow', 'bb', 'message-info cmd-abb'), ('a', 'yellow', 'c', 'message-info cmd-ac')) def test_suggestions_special(keyhint, config_stub): """Test that special characters work properly as prefix.""" bindings = {'normal': { 'a': 'message-info cmd-Cca', '': 'message-info cmd-CcCc', '': 'message-info cmd-CcCx', 'cbb': 'message-info cmd-cbb', 'xd': 'message-info cmd-xd', 'xe': 'message-info cmd-xe', }} default_bindings = {'normal': { 'c': 'message-info cmd-Ccc', }} config_stub.val.bindings.default = default_bindings config_stub.val.bindings.commands = bindings keyhint.update_keyhint(usertypes.KeyMode.normal, '') assert keyhint.text() == expected_text( ('<Ctrl+c>', 'yellow', 'a', 'message-info cmd-Cca'), ('<Ctrl+c>', 'yellow', 'c', 'message-info cmd-Ccc'), ('<Ctrl+c>', 'yellow', '<Ctrl+c>', 'message-info cmd-CcCc'), ('<Ctrl+c>', 'yellow', '<Ctrl+x>', 'message-info cmd-CcCx')) def test_suggestions_with_count(keyhint, config_stub, monkeypatch, stubs): """Test that a count prefix filters out commands that take no count.""" monkeypatch.setattr(objects, 'commands', { 'foo': stubs.FakeCommand(name='foo', takes_count=lambda: False), 'bar': stubs.FakeCommand(name='bar', takes_count=lambda: True), }) bindings = {'normal': {'aa': 'foo', 'ab': 'bar'}} config_stub.val.bindings.default = bindings config_stub.val.bindings.commands = bindings keyhint.update_keyhint(usertypes.KeyMode.normal, '2a') assert keyhint.text() == expected_text( ('a', 'yellow', 'b', 'bar'), ) def test_special_bindings(keyhint, config_stub): """Ensure a prefix of '<' doesn't suggest special keys.""" bindings = {'normal': { '': 'message-info cmd-ctrla', }} config_stub.val.bindings.default = {} config_stub.val.bindings.commands = bindings keyhint.update_keyhint(usertypes.KeyMode.normal, '<') assert keyhint.text() == expected_text( ('<', 'yellow', 'a', 'message-info cmd-<a'), ('<', 'yellow', 'b', 'message-info cmd-<b')) def test_color_switch(keyhint, config_stub): """Ensure the keyhint suffix color can be updated at runtime.""" bindings = {'normal': {'aa': 'message-info cmd-aa'}} config_stub.val.colors.keyhint.suffix.fg = '#ABCDEF' config_stub.val.bindings.default = {} config_stub.val.bindings.commands = bindings keyhint.update_keyhint(usertypes.KeyMode.normal, 'a') assert keyhint.text() == expected_text(('a', '#ABCDEF', 'a', 'message-info cmd-aa')) def test_no_matches(keyhint, config_stub): """Ensure the widget isn't visible if there are no keystrings to show.""" bindings = {'normal': { 'aa': 'message-info cmd-aa', 'ab': 'message-info cmd-ab', }} config_stub.val.bindings.default = {} config_stub.val.bindings.commands = bindings keyhint.update_keyhint(usertypes.KeyMode.normal, 'z') assert not keyhint.text() assert not keyhint.isVisible() @pytest.mark.parametrize('blacklist, expected', [ (['ab*'], expected_text(('a', 'yellow', 'a', 'message-info cmd-aa'))), (['*'], ''), ]) def test_blacklist(keyhint, config_stub, blacklist, expected): """Test that blacklisted keychains aren't hinted.""" config_stub.val.keyhint.blacklist = blacklist bindings = {'normal': { 'aa': 'message-info cmd-aa', 'ab': 'message-info cmd-ab', 'aba': 'message-info cmd-aba', 'abb': 'message-info cmd-abb', 'xd': 'message-info cmd-xd', 'xe': 'message-info cmd-xe', }} config_stub.val.bindings.default = {} config_stub.val.bindings.commands = bindings keyhint.update_keyhint(usertypes.KeyMode.normal, 'a') assert keyhint.text() == expected def test_delay(qtbot, stubs, monkeypatch, config_stub, key_config_stub): timer = stubs.FakeTimer() monkeypatch.setattr( 'qutebrowser.misc.keyhintwidget.usertypes.Timer', lambda *_: timer) interval = 200 bindings = {'normal': {'aa': 'message-info cmd-aa'}} config_stub.val.keyhint.delay = interval config_stub.val.bindings.default = {} config_stub.val.bindings.commands = bindings keyhint = KeyHintView(0, None) keyhint.update_keyhint(usertypes.KeyMode.normal, 'a') assert timer.isSingleShot() assert timer.interval() == interval