diff --git a/tests/helpers/fixtures.py b/tests/helpers/fixtures.py index 2530a1ab6..eb8dd9a97 100644 --- a/tests/helpers/fixtures.py +++ b/tests/helpers/fixtures.py @@ -255,6 +255,14 @@ def app_stub(stubs): objreg.delete('app') +@pytest.yield_fixture +def completion_widget_stub(win_registry): + stub = unittest.mock.Mock() + objreg.register('completion', stub, scope='window', window=0) + yield stub + objreg.delete('completion', scope='window', window=0) + + @pytest.fixture(scope='session') def stubs(): """Provide access to stub objects useful for testing.""" diff --git a/tests/helpers/stubs.py b/tests/helpers/stubs.py index fa7f60fa5..f51ec4cf1 100644 --- a/tests/helpers/stubs.py +++ b/tests/helpers/stubs.py @@ -293,12 +293,13 @@ class FakeCommand: """A simple command stub which has a description.""" def __init__(self, name='', desc='', hide=False, debug=False, - deprecated=False): + deprecated=False, completion=None): self.desc = desc self.name = name self.hide = hide self.debug = debug self.deprecated = deprecated + self.completion = completion class FakeTimer(QObject): @@ -359,6 +360,34 @@ class FakeConfigType: self.complete = lambda: [(val, '') for val in valid_values] +class FakeStatusbarCommand(QObject): + + """Stub for the statusbar command prompt.""" + + got_cmd = pyqtSignal(str) + clear_completion_selection = pyqtSignal() + hide_completion = pyqtSignal() + update_completion = pyqtSignal() + show_cmd = pyqtSignal() + hide_cmd = pyqtSignal() + textChanged = pyqtSignal() + + def __init__(self, parent=None, name=None): + super().__init__(parent) + self._cursor_pos = 0 + self._text = "" + self.cursorPosition = lambda: self._cursor_pos + self.text = lambda: self._text + self.prefix = lambda: self._text[0] + self.setFocus = lambda: None + + def setText(self, x): + self._text = x + + def setCursorPosition(self, x): + self._cursor_pos = x + + class ConfigStub(QObject): """Stub for the config module. diff --git a/tests/unit/completion/test_completer.py b/tests/unit/completion/test_completer.py new file mode 100644 index 000000000..b006a72ee --- /dev/null +++ b/tests/unit/completion/test_completer.py @@ -0,0 +1,136 @@ +# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: + +# Copyright 2016 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 . + +"""Tests for the Completer Object.""" + +from unittest.mock import Mock + +import pytest +from PyQt5.QtGui import QStandardItemModel + +from qutebrowser.completion.completer import Completer +from qutebrowser.utils.usertypes import Completion + + +class FakeCompletionModel(QStandardItemModel): + + """Stub for a completion model.""" + + DUMB_SORT = Mock() + + def __init__(self, kind, parent=None): + super().__init__(parent) + self.kind = kind + + +@pytest.fixture +def cmd(stubs): + """Create the statusbar command prompt the completer uses.""" + return stubs.FakeStatusbarCommand() + + +@pytest.fixture +def completer(qtbot, cmd, config_stub): + """Create the completer used for testing.""" + config_stub.data = {'completion': {'auto-open': False}} + return Completer(cmd, 0) + + +@pytest.fixture +def instances(monkeypatch): + """Mock the instances module so get returns a fake completion model.""" + # populate a model for each completion type, with a nested structure for + # option and value completion + instances = {kind: FakeCompletionModel(kind) for kind in Completion} + instances[Completion.option] = { + 'general': FakeCompletionModel(Completion.option), + } + instances[Completion.value] = { + 'general': { + 'ignore-case': FakeCompletionModel(Completion.value), + } + } + monkeypatch.setattr('qutebrowser.completion.completer.instances', + instances) + return instances + + +@pytest.fixture +def cmdutils_patch(monkeypatch, stubs): + """Patch the cmdutils module to provide fake commands.""" + cmds = { + 'set': [Completion.section, Completion.option, Completion.value], + 'help': [Completion.helptopic], + 'quickmark-load': [Completion.quickmark_by_name], + 'bookmark-load': [Completion.bookmark_by_url], + 'open': [Completion.url], + 'buffer': [Completion.tab], + 'session-load': [Completion.sessions], + 'bind': [Completion.empty, Completion.command], + } + cmd_utils = stubs.FakeCmdUtils({ + name: stubs.FakeCommand(completion=compl) + for name, compl in cmds.items() + }) + monkeypatch.setattr('qutebrowser.completion.completer.cmdutils', + cmd_utils) + return cmd_utils + + +@pytest.mark.parametrize('txt, expected', [ + (':nope|', Completion.command), + (':nope |', None), + (':set |', Completion.section), + (':set gen|', Completion.section), + (':set general |', Completion.option), + (':set what |', None), + (':set general ignore-case |', Completion.value), + (':set general huh |', None), + (':help |', Completion.helptopic), + (':quickmark-load |', Completion.quickmark_by_name), + (':bookmark-load |', Completion.bookmark_by_url), + (':open |', Completion.url), + (':buffer |', Completion.tab), + (':session-load |', Completion.sessions), + (':bind |', Completion.empty), + (':bind |', Completion.command), + (':bind foo|', Completion.command), + (':bind | foo', Completion.empty), + (':set| general ', Completion.command), + (':|set general ', Completion.command), + (':set gene|ral ignore-case', Completion.section), + (':|', Completion.command), + (': |', Completion.command), + (':bookmark-load |', Completion.bookmark_by_url), +]) +def test_update_completion(txt, expected, cmd, completer, instances, + cmdutils_patch, completion_widget_stub): + """Test setting the completion widget's model based on command text.""" + # this test uses | as a placeholder for the current cursor position + cursor_pos = txt.index('|') + cmd.setText(txt.replace('|', '')) + cmd.setCursorPosition(cursor_pos) + completer.update_completion() + if expected is None: + assert not completion_widget_stub.set_model.called + else: + assert completion_widget_stub.set_model.call_count == 1 + arg = completion_widget_stub.set_model.call_args[0][0] + # the outer model is just for sorting; srcmodel is the completion model + assert arg.srcmodel.kind == expected