Refactor readline tests.
They now use a real QLineEdit and verify a lot more. See #660, #678.
This commit is contained in:
parent
ad181ec7eb
commit
341708f543
@ -21,144 +21,256 @@
|
|||||||
|
|
||||||
# pylint: disable=protected-access
|
# pylint: disable=protected-access
|
||||||
|
|
||||||
|
import re
|
||||||
import inspect
|
import inspect
|
||||||
from unittest import mock
|
|
||||||
|
|
||||||
from PyQt5.QtWidgets import QLineEdit
|
from PyQt5.QtWidgets import QLineEdit, QApplication
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from qutebrowser.misc import readline
|
from qutebrowser.misc import readline
|
||||||
|
|
||||||
|
|
||||||
|
# Some functions aren't 100% readline compatible:
|
||||||
|
# https://github.com/The-Compiler/qutebrowser/issues/678
|
||||||
|
# Those are marked with fixme and have another value marked with '# wrong'
|
||||||
|
# which marks the current behavior.
|
||||||
|
|
||||||
|
fixme = pytest.mark.xfail(reason='readline compatibility - see #678')
|
||||||
|
|
||||||
|
|
||||||
|
class LineEdit(QLineEdit):
|
||||||
|
|
||||||
|
"""QLineEdit with some methods to make testing easier."""
|
||||||
|
|
||||||
|
def _get_index(self, haystack, needle):
|
||||||
|
"""Get the index of a char (needle) in a string (haystack).
|
||||||
|
|
||||||
|
Return:
|
||||||
|
The position where needle was found, or None if it wasn't found.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
return haystack.index(needle)
|
||||||
|
except ValueError:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def set_aug_text(self, text):
|
||||||
|
"""Set a text with </> markers for selected text and | as cursor."""
|
||||||
|
real_text = re.sub('[<>|]', '', text)
|
||||||
|
self.setText(real_text)
|
||||||
|
|
||||||
|
cursor_pos = self._get_index(text, '|')
|
||||||
|
sel_start_pos = self._get_index(text, '<')
|
||||||
|
sel_end_pos = self._get_index(text, '>')
|
||||||
|
|
||||||
|
if sel_start_pos is not None and sel_end_pos is None:
|
||||||
|
raise ValueError("< given without >!")
|
||||||
|
if sel_start_pos is None and sel_end_pos is not None:
|
||||||
|
raise ValueError("> given without <!")
|
||||||
|
|
||||||
|
if cursor_pos is not None:
|
||||||
|
if sel_start_pos is not None or sel_end_pos is not None:
|
||||||
|
raise ValueError("Can't mix | and </>!")
|
||||||
|
self.setCursorPosition(cursor_pos)
|
||||||
|
elif sel_start_pos is not None:
|
||||||
|
if sel_start_pos > sel_end_pos:
|
||||||
|
raise ValueError("< given after >!")
|
||||||
|
sel_len = sel_end_pos - sel_start_pos - 1
|
||||||
|
self.setSelection(sel_start_pos, sel_len)
|
||||||
|
|
||||||
|
def aug_text(self):
|
||||||
|
"""Get a text with </> markers for selected text and | as cursor."""
|
||||||
|
text = self.text()
|
||||||
|
chars = list(text)
|
||||||
|
cur_pos = self.cursorPosition()
|
||||||
|
assert cur_pos >= 0
|
||||||
|
chars.insert(cur_pos, '|')
|
||||||
|
if self.hasSelectedText():
|
||||||
|
selected_text = self.selectedText()
|
||||||
|
sel_start = self.selectionStart()
|
||||||
|
sel_end = sel_start + len(selected_text)
|
||||||
|
assert sel_start > 0
|
||||||
|
assert sel_end > 0
|
||||||
|
assert sel_end > sel_start
|
||||||
|
assert cur_pos == sel_end
|
||||||
|
assert text[sel_start:sel_end] == selected_text
|
||||||
|
chars.insert(sel_start, '<')
|
||||||
|
chars.insert(sel_end + 1, '>')
|
||||||
|
return ''.join(chars)
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def mocked_qapp(monkeypatch, stubs):
|
def lineedit(qtbot, monkeypatch):
|
||||||
"""Fixture that mocks readline.QApplication and returns it."""
|
"""Fixture providing a LineEdit."""
|
||||||
stub = stubs.FakeQApplication()
|
le = LineEdit()
|
||||||
monkeypatch.setattr('qutebrowser.misc.readline.QApplication', stub)
|
qtbot.add_widget(le)
|
||||||
return stub
|
monkeypatch.setattr(QApplication.instance(), 'focusWidget', lambda: le)
|
||||||
|
return le
|
||||||
|
|
||||||
|
|
||||||
class TestNoneWidget:
|
@pytest.fixture
|
||||||
|
def bridge():
|
||||||
"""Test if there are no exceptions when the widget is None."""
|
"""Fixture providing a ReadlineBridge."""
|
||||||
|
return readline.ReadlineBridge()
|
||||||
def test_none(self, mocked_qapp):
|
|
||||||
"""Call each rl_* method with a None focusWidget."""
|
|
||||||
self.bridge = readline.ReadlineBridge()
|
|
||||||
mocked_qapp.focusWidget = mock.Mock(return_value=None)
|
|
||||||
for name, method in inspect.getmembers(self.bridge, inspect.ismethod):
|
|
||||||
if name.startswith('rl_'):
|
|
||||||
method()
|
|
||||||
|
|
||||||
|
|
||||||
class TestReadlineBridgeTest:
|
def test_none(bridge, qtbot):
|
||||||
|
"""Call each rl_* method with a None focusWidget."""
|
||||||
|
assert QApplication.instance().focusWidget() is None
|
||||||
|
for name, method in inspect.getmembers(bridge, inspect.ismethod):
|
||||||
|
if name.startswith('rl_'):
|
||||||
|
method()
|
||||||
|
|
||||||
"""Tests for readline bridge."""
|
|
||||||
|
|
||||||
@pytest.fixture(autouse=True)
|
@pytest.mark.parametrize('text, expected', [('f<oo>bar', 'fo|obar'),
|
||||||
def setup(self):
|
('|foobar', '|foobar')])
|
||||||
self.qle = mock.Mock()
|
def test_rl_backward_char(text, expected, lineedit, bridge):
|
||||||
self.qle.__class__ = QLineEdit
|
"""Test rl_backward_char."""
|
||||||
self.bridge = readline.ReadlineBridge()
|
lineedit.set_aug_text(text)
|
||||||
|
bridge.rl_backward_char()
|
||||||
|
assert lineedit.aug_text() == expected
|
||||||
|
|
||||||
def _set_selected_text(self, text):
|
|
||||||
"""Set the value the fake QLineEdit should return for selectedText."""
|
|
||||||
self.qle.configure_mock(**{'selectedText.return_value': text})
|
|
||||||
|
|
||||||
def test_rl_backward_char(self, mocked_qapp):
|
@pytest.mark.parametrize('text, expected', [('f<oo>bar', 'foob|ar'),
|
||||||
"""Test rl_backward_char."""
|
('foobar|', 'foobar|')])
|
||||||
mocked_qapp.focusWidget = mock.Mock(return_value=self.qle)
|
def test_rl_forward_char(text, expected, lineedit, bridge):
|
||||||
self.bridge.rl_backward_char()
|
"""Test rl_forward_char."""
|
||||||
self.qle.cursorBackward.assert_called_with(False)
|
lineedit.set_aug_text(text)
|
||||||
|
bridge.rl_forward_char()
|
||||||
|
assert lineedit.aug_text() == expected
|
||||||
|
|
||||||
def test_rl_forward_char(self, mocked_qapp):
|
|
||||||
"""Test rl_forward_char."""
|
|
||||||
mocked_qapp.focusWidget = mock.Mock(return_value=self.qle)
|
|
||||||
self.bridge.rl_forward_char()
|
|
||||||
self.qle.cursorForward.assert_called_with(False)
|
|
||||||
|
|
||||||
def test_rl_backward_word(self, mocked_qapp):
|
@pytest.mark.parametrize('text, expected', [('one <tw>o', 'one |two'),
|
||||||
"""Test rl_backward_word."""
|
('<one >two', '|one two'),
|
||||||
mocked_qapp.focusWidget = mock.Mock(return_value=self.qle)
|
('|one two', '|one two')])
|
||||||
self.bridge.rl_backward_word()
|
def test_rl_backward_word(text, expected, lineedit, bridge):
|
||||||
self.qle.cursorWordBackward.assert_called_with(False)
|
"""Test rl_backward_word."""
|
||||||
|
lineedit.set_aug_text(text)
|
||||||
|
bridge.rl_backward_word()
|
||||||
|
assert lineedit.aug_text() == expected
|
||||||
|
|
||||||
def test_rl_forward_word(self, mocked_qapp):
|
|
||||||
"""Test rl_forward_word."""
|
|
||||||
mocked_qapp.focusWidget = mock.Mock(return_value=self.qle)
|
|
||||||
self.bridge.rl_forward_word()
|
|
||||||
self.qle.cursorWordForward.assert_called_with(False)
|
|
||||||
|
|
||||||
def test_rl_beginning_of_line(self, mocked_qapp):
|
@pytest.mark.parametrize('text, expected', [
|
||||||
"""Test rl_beginning_of_line."""
|
fixme(('<o>ne two', 'one| two')),
|
||||||
mocked_qapp.focusWidget = mock.Mock(return_value=self.qle)
|
('<o>ne two', 'one |two'), # wrong
|
||||||
self.bridge.rl_beginning_of_line()
|
fixme(('<one> two', 'one two|')),
|
||||||
self.qle.home.assert_called_with(False)
|
('<one> two', 'one |two'), # wrong
|
||||||
|
('one t<wo>', 'one two|')
|
||||||
|
])
|
||||||
|
def test_rl_forward_word(text, expected, lineedit, bridge):
|
||||||
|
"""Test rl_forward_word."""
|
||||||
|
lineedit.set_aug_text(text)
|
||||||
|
bridge.rl_forward_word()
|
||||||
|
assert lineedit.aug_text() == expected
|
||||||
|
|
||||||
def test_rl_end_of_line(self, mocked_qapp):
|
|
||||||
"""Test rl_end_of_line."""
|
|
||||||
mocked_qapp.focusWidget = mock.Mock(return_value=self.qle)
|
|
||||||
self.bridge.rl_end_of_line()
|
|
||||||
self.qle.end.assert_called_with(False)
|
|
||||||
|
|
||||||
def test_rl_delete_char(self, mocked_qapp):
|
def test_rl_beginning_of_line(lineedit, bridge):
|
||||||
"""Test rl_delete_char."""
|
"""Test rl_beginning_of_line."""
|
||||||
mocked_qapp.focusWidget = mock.Mock(return_value=self.qle)
|
lineedit.set_aug_text('f<oo>bar')
|
||||||
self.bridge.rl_delete_char()
|
bridge.rl_beginning_of_line()
|
||||||
self.qle.del_.assert_called_with()
|
assert lineedit.aug_text() == '|foobar'
|
||||||
|
|
||||||
def test_rl_backward_delete_char(self, mocked_qapp):
|
|
||||||
"""Test rl_backward_delete_char."""
|
|
||||||
mocked_qapp.focusWidget = mock.Mock(return_value=self.qle)
|
|
||||||
self.bridge.rl_backward_delete_char()
|
|
||||||
self.qle.backspace.assert_called_with()
|
|
||||||
|
|
||||||
def test_rl_unix_line_discard(self, mocked_qapp):
|
def test_rl_end_of_line(lineedit, bridge):
|
||||||
"""Set a selected text, delete it, see if it comes back with yank."""
|
"""Test rl_end_of_line."""
|
||||||
mocked_qapp.focusWidget = mock.Mock(return_value=self.qle)
|
lineedit.set_aug_text('f<oo>bar')
|
||||||
self._set_selected_text("delete test")
|
bridge.rl_end_of_line()
|
||||||
self.bridge.rl_unix_line_discard()
|
assert lineedit.aug_text() == 'foobar|'
|
||||||
self.qle.home.assert_called_with(True)
|
|
||||||
assert self.bridge._deleted[self.qle] == "delete test"
|
|
||||||
self.qle.del_.assert_called_with()
|
|
||||||
self.bridge.rl_yank()
|
|
||||||
self.qle.insert.assert_called_with("delete test")
|
|
||||||
|
|
||||||
def test_rl_kill_line(self, mocked_qapp):
|
|
||||||
"""Set a selected text, delete it, see if it comes back with yank."""
|
|
||||||
mocked_qapp.focusWidget = mock.Mock(return_value=self.qle)
|
|
||||||
self._set_selected_text("delete test")
|
|
||||||
self.bridge.rl_kill_line()
|
|
||||||
self.qle.end.assert_called_with(True)
|
|
||||||
assert self.bridge._deleted[self.qle] == "delete test"
|
|
||||||
self.qle.del_.assert_called_with()
|
|
||||||
self.bridge.rl_yank()
|
|
||||||
self.qle.insert.assert_called_with("delete test")
|
|
||||||
|
|
||||||
def test_rl_unix_word_rubout(self, mocked_qapp):
|
@pytest.mark.parametrize('text, expected', [('foo|bar', 'foo|ar'),
|
||||||
"""Set a selected text, delete it, see if it comes back with yank."""
|
('foobar|', 'foobar|'),
|
||||||
mocked_qapp.focusWidget = mock.Mock(return_value=self.qle)
|
('|foobar', '|oobar'),
|
||||||
self._set_selected_text("delete test")
|
('f<oo>bar', 'f|bar')])
|
||||||
self.bridge.rl_unix_word_rubout()
|
def test_rl_delete_char(text, expected, lineedit, bridge):
|
||||||
self.qle.cursorWordBackward.assert_called_with(True)
|
"""Test rl_delete_char."""
|
||||||
assert self.bridge._deleted[self.qle] == "delete test"
|
lineedit.set_aug_text(text)
|
||||||
self.qle.del_.assert_called_with()
|
bridge.rl_delete_char()
|
||||||
self.bridge.rl_yank()
|
assert lineedit.aug_text() == expected
|
||||||
self.qle.insert.assert_called_with("delete test")
|
|
||||||
|
|
||||||
def test_rl_kill_word(self, mocked_qapp):
|
|
||||||
"""Set a selected text, delete it, see if it comes back with yank."""
|
|
||||||
mocked_qapp.focusWidget = mock.Mock(return_value=self.qle)
|
|
||||||
self._set_selected_text("delete test")
|
|
||||||
self.bridge.rl_kill_word()
|
|
||||||
self.qle.cursorWordForward.assert_called_with(True)
|
|
||||||
assert self.bridge._deleted[self.qle] == "delete test"
|
|
||||||
self.qle.del_.assert_called_with()
|
|
||||||
self.bridge.rl_yank()
|
|
||||||
self.qle.insert.assert_called_with("delete test")
|
|
||||||
|
|
||||||
def test_rl_yank_no_text(self, mocked_qapp):
|
@pytest.mark.parametrize('text, expected', [('foo|bar', 'fo|bar'),
|
||||||
"""Test yank without having deleted anything."""
|
('foobar|', 'fooba|'),
|
||||||
mocked_qapp.focusWidget = mock.Mock(return_value=self.qle)
|
('|foobar', '|foobar'),
|
||||||
self.bridge.rl_yank()
|
('f<oo>bar', 'f|bar')])
|
||||||
assert not self.qle.insert.called
|
def test_rl_backward_delete_char(text, expected, lineedit, bridge):
|
||||||
|
"""Test rl_backward_delete_char."""
|
||||||
|
lineedit.set_aug_text(text)
|
||||||
|
bridge.rl_backward_delete_char()
|
||||||
|
assert lineedit.aug_text() == expected
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('text, deleted, rest', [
|
||||||
|
('delete this| test', 'delete this', '| test'),
|
||||||
|
fixme(('delete <this> test', 'delete this', '| test')),
|
||||||
|
('delete <this> test', 'delete ', '|this test'), # wrong
|
||||||
|
fixme(('f<oo>bar', 'foo', '|bar')),
|
||||||
|
('f<oo>bar', 'f', '|oobar'), # wrong
|
||||||
|
])
|
||||||
|
def test_rl_unix_line_discard(lineedit, bridge, text, deleted, rest):
|
||||||
|
"""Delete from the cursor to the beginning of the line and yank back."""
|
||||||
|
lineedit.set_aug_text(text)
|
||||||
|
bridge.rl_unix_line_discard()
|
||||||
|
assert bridge._deleted[lineedit] == deleted
|
||||||
|
assert lineedit.aug_text() == rest
|
||||||
|
lineedit.clear()
|
||||||
|
bridge.rl_yank()
|
||||||
|
assert lineedit.aug_text() == deleted + '|'
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('text, deleted, rest', [
|
||||||
|
('test |delete this', 'delete this', 'test |'),
|
||||||
|
fixme(('<test >delete this', 'test delete this', 'test |')),
|
||||||
|
('<test >delete this', 'test delete this', '|'), # wrong
|
||||||
|
])
|
||||||
|
def test_rl_kill_line(lineedit, bridge, text, deleted, rest):
|
||||||
|
"""Delete from the cursor to the end of line and yank back."""
|
||||||
|
lineedit.set_aug_text(text)
|
||||||
|
bridge.rl_kill_line()
|
||||||
|
assert bridge._deleted[lineedit] == deleted
|
||||||
|
assert lineedit.aug_text() == rest
|
||||||
|
lineedit.clear()
|
||||||
|
bridge.rl_yank()
|
||||||
|
assert lineedit.aug_text() == deleted + '|'
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('text, deleted, rest', [
|
||||||
|
('test delete|foobar', 'delete', 'test |foobar'),
|
||||||
|
('test delete |foobar', 'delete ', 'test |foobar'),
|
||||||
|
fixme(('test del<ete>foobar', 'delete', 'test |foobar')),
|
||||||
|
('test del<ete >foobar', 'del', 'test |ete foobar'), # wrong
|
||||||
|
])
|
||||||
|
def test_rl_unix_word_rubout(lineedit, bridge, text, deleted, rest):
|
||||||
|
"""Delete to word beginning and see if it comes back with yank."""
|
||||||
|
lineedit.set_aug_text(text)
|
||||||
|
bridge.rl_unix_word_rubout()
|
||||||
|
assert bridge._deleted[lineedit] == deleted
|
||||||
|
assert lineedit.aug_text() == rest
|
||||||
|
lineedit.clear()
|
||||||
|
bridge.rl_yank()
|
||||||
|
assert lineedit.aug_text() == deleted + '|'
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('text, deleted, rest', [
|
||||||
|
fixme(('test foobar| delete', ' delete', 'test foobar|')),
|
||||||
|
('test foobar| delete', ' ', 'test foobar|delete'), # wrong
|
||||||
|
fixme(('test foo|delete bar', 'delete', 'test foo| bar')),
|
||||||
|
('test foo|delete bar', 'delete ', 'test foo|bar'), # wrong
|
||||||
|
fixme(('test foo<bar> delete', ' delete', 'test foobar|')),
|
||||||
|
('test foo<bar>delete', 'bardelete', 'test foo|'), # wrong
|
||||||
|
])
|
||||||
|
def test_rl_kill_word(lineedit, bridge, text, deleted, rest):
|
||||||
|
"""Delete to word end and see if it comes back with yank."""
|
||||||
|
lineedit.set_aug_text(text)
|
||||||
|
bridge.rl_kill_word()
|
||||||
|
assert bridge._deleted[lineedit] == deleted
|
||||||
|
assert lineedit.aug_text() == rest
|
||||||
|
lineedit.clear()
|
||||||
|
bridge.rl_yank()
|
||||||
|
assert lineedit.aug_text() == deleted + '|'
|
||||||
|
|
||||||
|
|
||||||
|
def test_rl_yank_no_text(lineedit, bridge):
|
||||||
|
"""Test yank without having deleted anything."""
|
||||||
|
lineedit.clear()
|
||||||
|
bridge.rl_yank()
|
||||||
|
assert lineedit.aug_text() == '|'
|
||||||
|
Loading…
Reference in New Issue
Block a user