2016-06-29 04:55:16 +02:00
|
|
|
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
|
|
|
|
|
|
|
# Copyright 2016 Ryan Roden-Corrent (rcorre) <ryan@rcorre.net>
|
|
|
|
#
|
|
|
|
# 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 <http://www.gnu.org/licenses/>.
|
|
|
|
|
|
|
|
"""Tests for the Completer Object."""
|
|
|
|
|
2016-07-04 04:58:09 +02:00
|
|
|
import unittest.mock
|
|
|
|
|
2016-06-29 04:55:16 +02:00
|
|
|
import pytest
|
2016-07-27 18:14:42 +02:00
|
|
|
from PyQt5.QtCore import QObject
|
2016-06-29 04:55:16 +02:00
|
|
|
from PyQt5.QtGui import QStandardItemModel
|
|
|
|
|
2016-07-03 19:03:30 +02:00
|
|
|
from qutebrowser.completion import completer
|
|
|
|
from qutebrowser.utils import usertypes
|
2016-06-08 02:20:56 +02:00
|
|
|
from qutebrowser.commands import command, cmdutils
|
2016-06-29 04:55:16 +02:00
|
|
|
|
|
|
|
|
|
|
|
class FakeCompletionModel(QStandardItemModel):
|
|
|
|
|
|
|
|
"""Stub for a completion model."""
|
|
|
|
|
2016-07-03 19:03:30 +02:00
|
|
|
DUMB_SORT = None
|
2016-06-29 04:55:16 +02:00
|
|
|
|
|
|
|
def __init__(self, kind, parent=None):
|
|
|
|
super().__init__(parent)
|
|
|
|
self.kind = kind
|
|
|
|
|
|
|
|
|
2016-07-27 18:14:42 +02:00
|
|
|
class CompletionWidgetStub(QObject):
|
|
|
|
|
|
|
|
"""Stub for the CompletionView."""
|
|
|
|
|
|
|
|
def __init__(self, parent=None):
|
|
|
|
super().__init__(parent)
|
|
|
|
self.hide = unittest.mock.Mock()
|
|
|
|
self.show = unittest.mock.Mock()
|
|
|
|
self.set_pattern = unittest.mock.Mock()
|
|
|
|
self.model = unittest.mock.Mock()
|
|
|
|
self.set_model = unittest.mock.Mock()
|
|
|
|
self.enabled = unittest.mock.Mock()
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.fixture
|
|
|
|
def completion_widget_stub():
|
|
|
|
return CompletionWidgetStub()
|
|
|
|
|
|
|
|
|
2016-06-29 04:55:16 +02:00
|
|
|
@pytest.fixture
|
2016-07-27 18:14:42 +02:00
|
|
|
def completer_obj(qtbot, status_command_stub, config_stub, monkeypatch, stubs,
|
|
|
|
completion_widget_stub):
|
2016-06-29 04:55:16 +02:00
|
|
|
"""Create the completer used for testing."""
|
2016-07-12 02:54:16 +02:00
|
|
|
monkeypatch.setattr('qutebrowser.completion.completer.QTimer',
|
|
|
|
stubs.InstaTimer)
|
2016-08-08 23:24:23 +02:00
|
|
|
config_stub.data = {'completion': {'show': 'auto'}}
|
2016-07-27 18:14:42 +02:00
|
|
|
return completer.Completer(status_command_stub, 0, completion_widget_stub)
|
2016-06-29 04:55:16 +02:00
|
|
|
|
|
|
|
|
2016-07-03 19:03:30 +02:00
|
|
|
@pytest.fixture(autouse=True)
|
2016-06-29 04:55:16 +02:00
|
|
|
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
|
2016-07-03 19:03:30 +02:00
|
|
|
instances = {kind: FakeCompletionModel(kind)
|
|
|
|
for kind in usertypes.Completion}
|
|
|
|
instances[usertypes.Completion.option] = {
|
|
|
|
'general': FakeCompletionModel(usertypes.Completion.option),
|
2016-06-29 04:55:16 +02:00
|
|
|
}
|
2016-07-03 19:03:30 +02:00
|
|
|
instances[usertypes.Completion.value] = {
|
2016-06-29 04:55:16 +02:00
|
|
|
'general': {
|
2016-07-03 19:03:30 +02:00
|
|
|
'ignore-case': FakeCompletionModel(usertypes.Completion.value),
|
2016-06-29 04:55:16 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
monkeypatch.setattr('qutebrowser.completion.completer.instances',
|
|
|
|
instances)
|
|
|
|
|
|
|
|
|
2016-07-03 19:03:30 +02:00
|
|
|
@pytest.fixture(autouse=True)
|
2016-06-29 04:55:16 +02:00
|
|
|
def cmdutils_patch(monkeypatch, stubs):
|
|
|
|
"""Patch the cmdutils module to provide fake commands."""
|
2016-06-08 02:20:56 +02:00
|
|
|
@cmdutils.argument('section_', completion=usertypes.Completion.section)
|
|
|
|
@cmdutils.argument('option', completion=usertypes.Completion.option)
|
|
|
|
@cmdutils.argument('value', completion=usertypes.Completion.value)
|
|
|
|
def set_command(section_=None, option=None, value=None):
|
2016-08-09 17:28:14 +02:00
|
|
|
"""docstring."""
|
2016-06-08 02:20:56 +02:00
|
|
|
pass
|
|
|
|
|
|
|
|
@cmdutils.argument('topic', completion=usertypes.Completion.helptopic)
|
|
|
|
def show_help(tab=False, bg=False, window=False, topic=None):
|
2016-08-09 17:28:14 +02:00
|
|
|
"""docstring."""
|
2016-06-08 02:20:56 +02:00
|
|
|
pass
|
|
|
|
|
|
|
|
@cmdutils.argument('url', completion=usertypes.Completion.url)
|
|
|
|
@cmdutils.argument('count', count=True)
|
|
|
|
def openurl(url=None, implicit=False, bg=False, tab=False, window=False,
|
|
|
|
count=None):
|
2016-08-09 17:28:14 +02:00
|
|
|
"""docstring."""
|
2016-06-08 02:20:56 +02:00
|
|
|
pass
|
|
|
|
|
|
|
|
@cmdutils.argument('win_id', win_id=True)
|
|
|
|
@cmdutils.argument('command', completion=usertypes.Completion.command)
|
|
|
|
def bind(key, win_id, command=None, *, mode='normal', force=False):
|
2016-08-09 17:28:14 +02:00
|
|
|
"""docstring."""
|
2016-06-08 02:20:56 +02:00
|
|
|
# pylint: disable=unused-variable
|
|
|
|
pass
|
|
|
|
|
|
|
|
def tab_detach():
|
2016-08-09 17:28:14 +02:00
|
|
|
"""docstring."""
|
2016-06-08 02:20:56 +02:00
|
|
|
pass
|
|
|
|
|
2016-06-29 04:55:16 +02:00
|
|
|
cmds = {
|
2016-06-08 02:20:56 +02:00
|
|
|
'set': set_command,
|
|
|
|
'help': show_help,
|
|
|
|
'open': openurl,
|
|
|
|
'bind': bind,
|
|
|
|
'tab-detach': tab_detach,
|
2016-06-29 04:55:16 +02:00
|
|
|
}
|
|
|
|
cmd_utils = stubs.FakeCmdUtils({
|
2016-06-08 02:20:56 +02:00
|
|
|
name: command.Command(name=name, handler=fn)
|
|
|
|
for name, fn in cmds.items()
|
2016-06-29 04:55:16 +02:00
|
|
|
})
|
2016-06-08 02:20:56 +02:00
|
|
|
monkeypatch.setattr('qutebrowser.completion.completer.cmdutils', cmd_utils)
|
2016-06-29 04:55:16 +02:00
|
|
|
|
|
|
|
|
2016-07-04 18:31:56 +02:00
|
|
|
def _set_cmd_prompt(cmd, txt):
|
|
|
|
"""Set the command prompt's text and cursor position.
|
|
|
|
|
|
|
|
Args:
|
|
|
|
cmd: The command prompt object.
|
|
|
|
txt: The prompt text, using | as a placeholder for the cursor position.
|
|
|
|
"""
|
|
|
|
cmd.setText(txt.replace('|', ''))
|
|
|
|
cmd.setCursorPosition(txt.index('|'))
|
|
|
|
|
|
|
|
|
2016-06-29 04:55:16 +02:00
|
|
|
@pytest.mark.parametrize('txt, expected', [
|
2016-07-03 19:03:30 +02:00
|
|
|
(':nope|', usertypes.Completion.command),
|
2016-06-29 04:55:16 +02:00
|
|
|
(':nope |', None),
|
2016-07-03 19:03:30 +02:00
|
|
|
(':set |', usertypes.Completion.section),
|
|
|
|
(':set gen|', usertypes.Completion.section),
|
|
|
|
(':set general |', usertypes.Completion.option),
|
2016-06-29 04:55:16 +02:00
|
|
|
(':set what |', None),
|
2016-07-03 19:03:30 +02:00
|
|
|
(':set general ignore-case |', usertypes.Completion.value),
|
2016-06-29 04:55:16 +02:00
|
|
|
(':set general huh |', None),
|
2016-07-03 19:03:30 +02:00
|
|
|
(':help |', usertypes.Completion.helptopic),
|
2016-06-08 02:20:56 +02:00
|
|
|
(':help |', usertypes.Completion.helptopic),
|
2016-07-03 19:03:30 +02:00
|
|
|
(':open |', usertypes.Completion.url),
|
2016-08-06 17:38:08 +02:00
|
|
|
(':bind |', None),
|
2016-07-03 19:03:30 +02:00
|
|
|
(':bind <c-x> |', usertypes.Completion.command),
|
|
|
|
(':bind <c-x> foo|', usertypes.Completion.command),
|
2016-08-06 17:38:08 +02:00
|
|
|
(':bind <c-x>| foo', None),
|
2016-07-03 19:03:30 +02:00
|
|
|
(':set| general ', usertypes.Completion.command),
|
|
|
|
(':|set general ', usertypes.Completion.command),
|
|
|
|
(':set gene|ral ignore-case', usertypes.Completion.section),
|
|
|
|
(':|', usertypes.Completion.command),
|
|
|
|
(': |', usertypes.Completion.command),
|
2016-07-03 22:42:29 +02:00
|
|
|
('/|', None),
|
|
|
|
(':open -t|', None),
|
|
|
|
(':open --tab|', None),
|
|
|
|
(':open -t |', usertypes.Completion.url),
|
|
|
|
(':open --tab |', usertypes.Completion.url),
|
|
|
|
(':open | -t', usertypes.Completion.url),
|
|
|
|
(':--foo --bar |', None),
|
|
|
|
(':tab-detach |', None),
|
|
|
|
(':bind --mode=caret <c-x> |', usertypes.Completion.command),
|
2016-07-04 13:02:37 +02:00
|
|
|
pytest.mark.xfail(reason='issue #74')((':bind --mode caret <c-x> |',
|
|
|
|
usertypes.Completion.command)),
|
2016-07-03 22:42:29 +02:00
|
|
|
(':set -t -p |', usertypes.Completion.section),
|
|
|
|
(':open -- |', None),
|
2016-06-29 04:55:16 +02:00
|
|
|
])
|
2016-07-12 18:47:28 +02:00
|
|
|
def test_update_completion(txt, expected, status_command_stub, completer_obj,
|
2016-07-03 19:03:30 +02:00
|
|
|
completion_widget_stub):
|
2016-06-29 04:55:16 +02:00
|
|
|
"""Test setting the completion widget's model based on command text."""
|
|
|
|
# this test uses | as a placeholder for the current cursor position
|
2016-07-12 18:47:28 +02:00
|
|
|
_set_cmd_prompt(status_command_stub, txt)
|
2016-07-12 02:54:16 +02:00
|
|
|
completer_obj.schedule_completion_update()
|
2016-08-09 19:03:38 +02:00
|
|
|
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
|
2016-06-29 04:55:16 +02:00
|
|
|
if expected is None:
|
2016-08-09 19:03:38 +02:00
|
|
|
assert arg == expected
|
2016-06-29 04:55:16 +02:00
|
|
|
else:
|
|
|
|
assert arg.srcmodel.kind == expected
|
2016-07-04 04:58:09 +02:00
|
|
|
|
2016-07-04 13:02:37 +02:00
|
|
|
|
2016-09-06 04:07:16 +02:00
|
|
|
@pytest.mark.parametrize('before, newtxt, after', [
|
|
|
|
(':|', 'set', ':set|'),
|
|
|
|
(':| ', 'set', ':set|'),
|
|
|
|
(': |', 'set', ':set|'),
|
|
|
|
(':|set', 'set', ':set|'),
|
|
|
|
(':|set ', 'set', ':set|'),
|
|
|
|
(':|se', 'set', ':set|'),
|
|
|
|
(':s|e', 'set', ':set|'),
|
|
|
|
(':se|', 'set', ':set|'),
|
|
|
|
(':|se fonts', 'set', ':set| fonts'),
|
|
|
|
(':set |', 'fonts', ':set fonts|'),
|
|
|
|
(':set |', 'fonts', ':set fonts|'),
|
|
|
|
(':set --temp |', 'fonts', ':set --temp fonts|'),
|
|
|
|
(':set |fo', 'fonts', ':set fonts|'),
|
|
|
|
(':set f|o', 'fonts', ':set fonts|'),
|
|
|
|
(':set fo|', 'fonts', ':set fonts|'),
|
|
|
|
(':set fonts |', 'hints', ':set fonts hints|'),
|
|
|
|
(':set fonts |nt', 'hints', ':set fonts hints|'),
|
|
|
|
(':set fonts n|t', 'hints', ':set fonts hints|'),
|
|
|
|
(':set fonts nt|', 'hints', ':set fonts hints|'),
|
|
|
|
(':set | hints', 'fonts', ':set fonts| hints'),
|
|
|
|
(':set | hints', 'fonts', ':set fonts| hints'),
|
|
|
|
(':set |fo hints', 'fonts', ':set fonts| hints'),
|
|
|
|
(':set f|o hints', 'fonts', ':set fonts| hints'),
|
|
|
|
(':set fo| hints', 'fonts', ':set fonts| hints'),
|
|
|
|
(':set fonts hints |', 'Comic Sans', ":set fonts hints 'Comic Sans'|"),
|
|
|
|
(":set fonts hints 'Comic Sans'|", '12px Hack',
|
|
|
|
":set fonts hints '12px Hack'|"),
|
|
|
|
(":set fonts hints 'Comic| Sans'", '12px Hack',
|
|
|
|
":set fonts hints '12px Hack'|")
|
2016-07-04 04:58:09 +02:00
|
|
|
])
|
2016-09-06 04:07:16 +02:00
|
|
|
def test_on_selection_changed(before, newtxt, after, completer_obj,
|
|
|
|
config_stub, status_command_stub,
|
|
|
|
completion_widget_stub):
|
2016-07-27 13:39:25 +02:00
|
|
|
"""Test that on_selection_changed modifies the cmd text properly.
|
2016-07-04 14:22:21 +02:00
|
|
|
|
|
|
|
The | represents the current cursor position in the cmd prompt.
|
|
|
|
If quick-complete is True and there is only 1 completion (count == 1),
|
|
|
|
then we expect a space to be appended after the current word.
|
|
|
|
"""
|
|
|
|
model = unittest.mock.Mock()
|
|
|
|
model.data = unittest.mock.Mock(return_value=newtxt)
|
|
|
|
indexes = [unittest.mock.Mock()]
|
|
|
|
selection = unittest.mock.Mock()
|
|
|
|
selection.indexes = unittest.mock.Mock(return_value=indexes)
|
2016-07-27 18:14:42 +02:00
|
|
|
completion_widget_stub.model.return_value = model
|
2016-09-06 04:07:16 +02:00
|
|
|
|
|
|
|
def check(quick_complete, count, expected_txt, expected_pos):
|
|
|
|
config_stub.data['completion']['quick-complete'] = quick_complete
|
|
|
|
model.count = unittest.mock.Mock(return_value=count)
|
|
|
|
_set_cmd_prompt(status_command_stub, before)
|
|
|
|
# TODO: refactor so cursor_part is a local rather than class variable
|
|
|
|
completer_obj._update_cursor_part()
|
|
|
|
completer_obj.on_selection_changed(selection)
|
|
|
|
model.data.assert_called_with(indexes[0])
|
|
|
|
assert status_command_stub.text() == expected_txt
|
|
|
|
assert status_command_stub.cursorPosition() == expected_pos
|
|
|
|
|
|
|
|
after_pos = after.index('|')
|
|
|
|
after_txt = after.replace('|', '')
|
|
|
|
check(False, 1, after_txt, after_pos)
|
|
|
|
check(True, 2, after_txt, after_pos)
|
|
|
|
|
|
|
|
# quick-completing a single item should move the cursor ahead by 1 and add
|
|
|
|
# a trailing space if at the end of the cmd string
|
|
|
|
after_pos += 1
|
|
|
|
if after_pos > len(after_txt):
|
|
|
|
after_txt += ' '
|
|
|
|
check(True, 1, after_txt, after_pos)
|