Refactor readline tests.

They now use a real QLineEdit and verify a lot more.

See #660, #678.
This commit is contained in:
Florian Bruhin 2015-05-19 12:29:53 +02:00
parent ad181ec7eb
commit 341708f543

View File

@ -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() == '|'