Merge branch 'hackebrot-change-test-layout-and-add-gui-tests'

This commit is contained in:
Florian Bruhin 2015-04-09 06:35:49 +02:00
commit 6c566198f1
55 changed files with 2335 additions and 2300 deletions

View File

@ -40,6 +40,7 @@ argument-rgx=[a-z_][a-z0-9_]{0,30}$
variable-rgx=[a-z_][a-z0-9_]{0,30}$ variable-rgx=[a-z_][a-z0-9_]{0,30}$
class-attribute-rgx=[A-Za-z_][A-Za-z0-9_]{1,30}$ class-attribute-rgx=[A-Za-z_][A-Za-z0-9_]{1,30}$
inlinevar-rgx=[a-z_][a-z0-9_]*$ inlinevar-rgx=[a-z_][a-z0-9_]*$
docstring-min-length=2
[FORMAT] [FORMAT]
max-line-length=79 max-line-length=79

View File

@ -6,7 +6,7 @@ graft icons
graft scripts/pylint_checkers graft scripts/pylint_checkers
graft doc/img graft doc/img
graft misc graft misc
include qutebrowser/test/testfile include qutebrowser/utils/testfile
include qutebrowser/git-commit-id include qutebrowser/git-commit-id
include COPYING doc/* README.asciidoc CONTRIBUTING.asciidoc FAQ.asciidoc INSTALL.asciidoc CHANGELOG.asciidoc include COPYING doc/* README.asciidoc CONTRIBUTING.asciidoc FAQ.asciidoc INSTALL.asciidoc CHANGELOG.asciidoc
include qutebrowser.desktop include qutebrowser.desktop
@ -23,7 +23,7 @@ exclude scripts/quit_segfault_test.sh
exclude scripts/segfault_test.sh exclude scripts/segfault_test.sh
exclude doc/notes exclude doc/notes
recursive-exclude doc *.asciidoc recursive-exclude doc *.asciidoc
prune test prune tests
exclude qutebrowser.rcc exclude qutebrowser.rcc
exclude .coveragerc exclude .coveragerc
exclude .flake8 exclude .flake8

View File

@ -134,6 +134,7 @@ Contributors, sorted by the number of commits in descending order:
// QUTE_AUTHORS_START // QUTE_AUTHORS_START
* Florian Bruhin * Florian Bruhin
* Bruno Oliveira
* Joel Torstensson * Joel Torstensson
* Raphael Pierzina * Raphael Pierzina
* Claude * Claude

View File

@ -1,20 +0,0 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# 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 qutebrowser.browser package."""

View File

@ -1,20 +0,0 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# 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 qutebrowser.browser.http module."""

View File

@ -1,140 +0,0 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# 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 webelement.tabhistory."""
import unittest
from PyQt5.QtCore import QUrl, QPoint
from qutebrowser.browser import tabhistory
from qutebrowser.browser.tabhistory import TabHistoryItem as Item
from qutebrowser.utils import qtutils
from qutebrowser.test import helpers
class SerializeHistoryTests(unittest.TestCase):
"""Tests for serialize()."""
def setUp(self):
self.page = helpers.get_webpage()
self.history = self.page.history()
self.assertEqual(self.history.count(), 0)
self.items = [
Item(QUrl('https://www.heise.de/'), QUrl('http://www.heise.de/'),
'heise'),
Item(QUrl('http://example.com/%E2%80%A6'),
QUrl('http://example.com/%E2%80%A6'), 'percent', active=True),
Item(QUrl('http://example.com/?foo=bar'),
QUrl('http://original.url.example.com/'), 'arg',
user_data={'foo': 23, 'bar': 42}),
# From https://github.com/OtterBrowser/otter-browser/issues/709#issuecomment-74749471
Item(QUrl('http://github.com/OtterBrowser/24/134/2344/otter-browser/issues/709/'),
QUrl('http://github.com/OtterBrowser/24/134/2344/otter-browser/issues/709/'),
'Page not found | github',
user_data={'zoom': 149, 'scroll-pos': QPoint(0, 0)}),
Item(QUrl('https://mail.google.com/mail/u/0/#label/some+label/234lkjsd0932lkjf884jqwerdf4'),
QUrl('https://mail.google.com/mail/u/0/#label/some+label/234lkjsd0932lkjf884jqwerdf4'),
'"some label" - email@gmail.com - Gmail"',
user_data={'zoom': 120, 'scroll-pos': QPoint(0, 0)}),
]
stream, _data, self.user_data = tabhistory.serialize(self.items)
qtutils.deserialize_stream(stream, self.history)
def test_count(self):
"""Check if the history's count was loaded correctly."""
with helpers.disable_logger('qt'):
self.assertEqual(self.history.count(), len(self.items))
def test_valid(self):
"""Check if all items are valid."""
for i, _item in enumerate(self.items):
self.assertTrue(self.history.itemAt(i).isValid())
def test_no_userdata(self):
"""Check if all items have no user data."""
for i, _item in enumerate(self.items):
self.assertIsNone(self.history.itemAt(i).userData())
def test_userdata(self):
"""Check if all user data has been restored to self.user_data."""
for item, user_data in zip(self.items, self.user_data):
self.assertEqual(user_data, item.user_data)
def test_currentitem(self):
"""Check if the current item index was loaded correctly."""
self.assertEqual(self.history.currentItemIndex(), 1)
def test_urls(self):
"""Check if the URLs were loaded correctly."""
for i, item in enumerate(self.items):
with self.subTest(i=i, item=item):
self.assertEqual(self.history.itemAt(i).url(), item.url)
def test_original_urls(self):
"""Check if the original URLs were loaded correctly."""
for i, item in enumerate(self.items):
with self.subTest(i=i, item=item):
self.assertEqual(self.history.itemAt(i).originalUrl(),
item.original_url)
def test_titles(self):
"""Check if the titles were loaded correctly."""
for i, item in enumerate(self.items):
with self.subTest(i=i, item=item):
self.assertEqual(self.history.itemAt(i).title(), item.title)
class SerializeHistorySpecialTests(unittest.TestCase):
"""Tests for serialize() without items set up in setUp."""
def setUp(self):
self.page = helpers.get_webpage()
self.history = self.page.history()
self.assertEqual(self.history.count(), 0)
def test_no_active_item(self):
"""Check tabhistory.serialize with no active item."""
items = [Item(QUrl(), QUrl(), '')]
with self.assertRaises(ValueError):
tabhistory.serialize(items)
def test_two_active_items(self):
"""Check tabhistory.serialize with two active items."""
items = [Item(QUrl(), QUrl(), '', active=True),
Item(QUrl(), QUrl(), ''),
Item(QUrl(), QUrl(), '', active=True)]
with self.assertRaises(ValueError):
tabhistory.serialize(items)
def test_empty(self):
"""Check tabhistory.serialize with no items."""
items = []
stream, _data, user_data = tabhistory.serialize(items)
qtutils.deserialize_stream(stream, self.history)
self.assertEqual(self.history.count(), 0)
self.assertEqual(self.history.currentItemIndex(), 0)
self.assertFalse(user_data)
if __name__ == '__main__':
unittest.main()

View File

@ -1 +0,0 @@
"""Tests for qutebrowser.config."""

View File

@ -1,109 +0,0 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# 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/>.
"""Helpers needed by tests."""
import os
import logging
import contextlib
from unittest import mock
from PyQt5.QtGui import QKeyEvent
from PyQt5.QtWebKitWidgets import QWebPage
from PyQt5.QtNetwork import QNetworkAccessManager
unicode_encode_err = UnicodeEncodeError('ascii', # codec
'', # object
0, # start
2, # end
'fake exception') # reason
@contextlib.contextmanager
def environ_set_temp(env):
"""Set temporary environment variables.
Args:
env: A dictionary with name: value pairs.
If value is None, the variable is temporarily deleted.
"""
old_env = {}
for name, value in env.items():
try:
old_env[name] = os.environ[name]
except KeyError:
pass
if value is None:
os.environ.pop(name, None)
else:
os.environ[name] = value
yield
for name, value in env.items():
if name in old_env:
os.environ[name] = old_env[name]
elif value is not None:
del os.environ[name]
@contextlib.contextmanager
def disable_logger(name):
"""Temporarily disable a logger."""
logging.getLogger(name).propagate = False
try:
yield
finally:
logging.getLogger(name).propagate = True
def fake_keyevent(key, modifiers=0, text=''):
"""Generate a new fake QKeyPressEvent."""
evtmock = mock.create_autospec(QKeyEvent, instance=True)
evtmock.key.return_value = key
evtmock.modifiers.return_value = modifiers
evtmock.text.return_value = text
return evtmock
def get_webpage():
"""Get a new QWebPage object."""
page = QWebPage()
nam = page.networkAccessManager()
nam.setNetworkAccessible(QNetworkAccessManager.NotAccessible)
return page
class MessageModule:
"""A drop-in replacement for qutebrowser.utils.message."""
def error(self, _win_id, message, _immediately=False):
"""Log an error to the message logger."""
logging.getLogger('message').error(message)
def warning(self, _win_id, message, _immediately=False):
"""Log a warning to the message logger."""
logging.getLogger('message').warning(message)
def info(self, _win_id, message, _immediately=True):
"""Log an info message to the message logger."""
logging.getLogger('message').info(message)

View File

@ -1 +0,0 @@
"""Tests for qutebrowser.keyinput."""

View File

@ -1,299 +0,0 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>:
#
# 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/>.
# pylint: disable=protected-access
"""Tests for BaseKeyParser."""
import logging
import unittest
from unittest import mock
from PyQt5.QtCore import Qt
from qutebrowser.keyinput import basekeyparser
from qutebrowser.test import stubs, helpers
from qutebrowser.utils import objreg, log
CONFIG = {'input': {'timeout': 100}}
BINDINGS = {'test': {'<Ctrl-a>': 'ctrla',
'a': 'a',
'ba': 'ba',
'ax': 'ax',
'ccc': 'ccc'},
'test2': {'foo': 'bar', '<Ctrl+X>': 'ctrlx'}}
fake_keyconfig = mock.Mock(spec=['get_bindings_for'])
fake_keyconfig.get_bindings_for.side_effect = lambda s: BINDINGS[s]
class SplitCountTests(unittest.TestCase):
"""Test the _split_count method.
Attributes:
kp: The BaseKeyParser we're testing.
"""
def setUp(self):
self.kp = basekeyparser.BaseKeyParser(0, supports_count=True)
def test_onlycount(self):
"""Test split_count with only a count."""
self.kp._keystring = '10'
self.assertEqual(self.kp._split_count(), (10, ''))
def test_normalcount(self):
"""Test split_count with count and text."""
self.kp._keystring = '10foo'
self.assertEqual(self.kp._split_count(), (10, 'foo'))
def test_minuscount(self):
"""Test split_count with a negative count."""
self.kp._keystring = '-1foo'
self.assertEqual(self.kp._split_count(), (None, '-1foo'))
def test_expcount(self):
"""Test split_count with an exponential count."""
self.kp._keystring = '10e4foo'
self.assertEqual(self.kp._split_count(), (10, 'e4foo'))
def test_nocount(self):
"""Test split_count with only a command."""
self.kp._keystring = 'foo'
self.assertEqual(self.kp._split_count(), (None, 'foo'))
def test_nosupport(self):
"""Test split_count with a count when counts aren't supported."""
self.kp._supports_count = False
self.kp._keystring = '10foo'
self.assertEqual(self.kp._split_count(), (None, '10foo'))
@mock.patch('qutebrowser.keyinput.basekeyparser.usertypes.Timer',
new=stubs.FakeTimer)
class ReadConfigTests(unittest.TestCase):
"""Test reading the config."""
def setUp(self):
objreg.register('key-config', fake_keyconfig)
def tearDown(self):
objreg.delete('key-config')
def test_read_config_invalid(self):
"""Test reading config without setting it before."""
kp = basekeyparser.BaseKeyParser(0)
with self.assertRaises(ValueError):
kp.read_config()
def test_read_config_valid(self):
"""Test reading config."""
kp = basekeyparser.BaseKeyParser(0, supports_count=True,
supports_chains=True)
kp.read_config('test')
self.assertIn('ccc', kp.bindings)
self.assertIn('ctrl+a', kp.special_bindings)
kp.read_config('test2')
self.assertNotIn('ccc', kp.bindings)
self.assertNotIn('ctrl+a', kp.special_bindings)
self.assertIn('foo', kp.bindings)
self.assertIn('ctrl+x', kp.special_bindings)
@mock.patch('qutebrowser.keyinput.basekeyparser.usertypes.Timer',
new=stubs.FakeTimer)
class SpecialKeysTests(unittest.TestCase):
"""Check execute() with special keys.
Attributes:
kp: The BaseKeyParser to be tested.
"""
def setUp(self):
objreg.register('key-config', fake_keyconfig)
self.kp = basekeyparser.BaseKeyParser(0)
self.kp.execute = mock.Mock()
with self.assertLogs(log.keyboard, logging.WARNING):
# Ignoring keychain 'ccc' in mode 'test' because keychains are not
# supported there.
self.kp.read_config('test')
def tearDown(self):
objreg.delete('key-config')
def test_valid_key(self):
"""Test a valid special keyevent."""
self.kp.handle(helpers.fake_keyevent(Qt.Key_A, Qt.ControlModifier))
self.kp.handle(helpers.fake_keyevent(Qt.Key_X, Qt.ControlModifier))
self.kp.execute.assert_called_once_with('ctrla', self.kp.Type.special)
def test_invalid_key(self):
"""Test an invalid special keyevent."""
self.kp.handle(helpers.fake_keyevent(Qt.Key_A, (Qt.ControlModifier |
Qt.AltModifier)))
self.assertFalse(self.kp.execute.called)
def test_keychain(self):
"""Test a keychain."""
self.kp.handle(helpers.fake_keyevent(Qt.Key_B))
self.kp.handle(helpers.fake_keyevent(Qt.Key_A))
self.assertFalse(self.kp.execute.called)
@mock.patch('qutebrowser.keyinput.basekeyparser.usertypes.Timer',
new=stubs.FakeTimer)
class KeyChainTests(unittest.TestCase):
"""Test execute() with keychain support.
Attributes:
kp: The BaseKeyParser to be tested.
"""
def setUp(self):
"""Set up mocks and read the test config."""
objreg.register('key-config', fake_keyconfig)
self.kp = basekeyparser.BaseKeyParser(0, supports_chains=True,
supports_count=False)
self.kp.execute = mock.Mock()
self.kp.read_config('test')
def tearDown(self):
objreg.delete('key-config')
def test_valid_special_key(self):
"""Test valid special key."""
self.kp.handle(helpers.fake_keyevent(Qt.Key_A, Qt.ControlModifier))
self.kp.handle(helpers.fake_keyevent(Qt.Key_X, Qt.ControlModifier))
self.kp.execute.assert_called_once_with('ctrla', self.kp.Type.special)
self.assertEqual(self.kp._keystring, '')
def test_invalid_special_key(self):
"""Test invalid special key."""
self.kp.handle(helpers.fake_keyevent(Qt.Key_A, (Qt.ControlModifier |
Qt.AltModifier)))
self.assertFalse(self.kp.execute.called)
self.assertEqual(self.kp._keystring, '')
def test_keychain(self):
"""Test valid keychain."""
# Press 'x' which is ignored because of no match
self.kp.handle(helpers.fake_keyevent(Qt.Key_X, text='x'))
# Then start the real chain
self.kp.handle(helpers.fake_keyevent(Qt.Key_B, text='b'))
self.kp.handle(helpers.fake_keyevent(Qt.Key_A, text='a'))
self.kp.execute.assert_called_once_with('ba', self.kp.Type.chain, None)
self.assertEqual(self.kp._keystring, '')
@mock.patch('qutebrowser.keyinput.basekeyparser.config',
new=stubs.ConfigStub(CONFIG))
def test_ambiguous_keychain(self):
"""Test ambiguous keychain."""
timer = self.kp._ambiguous_timer
self.assertFalse(timer.isActive())
# We start with 'a' where the keychain gives us an ambiguous result.
# Then we check if the timer has been set up correctly
self.kp.handle(helpers.fake_keyevent(Qt.Key_A, text='a'))
self.assertFalse(self.kp.execute.called)
self.assertTrue(timer.isSingleShot())
self.assertEqual(timer.interval(), 100)
self.assertTrue(timer.isActive())
# Now we type an 'x' and check 'ax' has been executed and the timer
# stopped.
self.kp.handle(helpers.fake_keyevent(Qt.Key_X, text='x'))
self.kp.execute.assert_called_once_with('ax', self.kp.Type.chain, None)
self.assertFalse(timer.isActive())
self.assertEqual(self.kp._keystring, '')
def test_invalid_keychain(self):
"""Test invalid keychain."""
self.kp.handle(helpers.fake_keyevent(Qt.Key_B, text='b'))
self.kp.handle(helpers.fake_keyevent(Qt.Key_C, text='c'))
self.assertEqual(self.kp._keystring, '')
@mock.patch('qutebrowser.keyinput.basekeyparser.usertypes.Timer',
new=stubs.FakeTimer)
class CountTests(unittest.TestCase):
"""Test execute() with counts."""
def setUp(self):
objreg.register('key-config', fake_keyconfig)
self.kp = basekeyparser.BaseKeyParser(0, supports_chains=True,
supports_count=True)
self.kp.execute = mock.Mock()
self.kp.read_config('test')
def test_no_count(self):
"""Test with no count added."""
self.kp.handle(helpers.fake_keyevent(Qt.Key_B, text='b'))
self.kp.handle(helpers.fake_keyevent(Qt.Key_A, text='a'))
self.kp.execute.assert_called_once_with('ba', self.kp.Type.chain, None)
self.assertEqual(self.kp._keystring, '')
def test_count_0(self):
"""Test with count=0."""
self.kp.handle(helpers.fake_keyevent(Qt.Key_0, text='0'))
self.kp.handle(helpers.fake_keyevent(Qt.Key_B, text='b'))
self.kp.handle(helpers.fake_keyevent(Qt.Key_A, text='a'))
self.kp.execute.assert_called_once_with('ba', self.kp.Type.chain, 0)
self.assertEqual(self.kp._keystring, '')
def test_count_42(self):
"""Test with count=42."""
self.kp.handle(helpers.fake_keyevent(Qt.Key_4, text='4'))
self.kp.handle(helpers.fake_keyevent(Qt.Key_2, text='2'))
self.kp.handle(helpers.fake_keyevent(Qt.Key_B, text='b'))
self.kp.handle(helpers.fake_keyevent(Qt.Key_A, text='a'))
self.kp.execute.assert_called_once_with('ba', self.kp.Type.chain, 42)
self.assertEqual(self.kp._keystring, '')
def test_count_42_invalid(self):
"""Test with count=42 and invalid command."""
# Invalid call with ccx gets ignored
self.kp.handle(helpers.fake_keyevent(Qt.Key_4, text='4'))
self.kp.handle(helpers.fake_keyevent(Qt.Key_2, text='2'))
self.kp.handle(helpers.fake_keyevent(Qt.Key_B, text='c'))
self.kp.handle(helpers.fake_keyevent(Qt.Key_A, text='c'))
self.kp.handle(helpers.fake_keyevent(Qt.Key_A, text='x'))
self.assertFalse(self.kp.execute.called)
self.assertEqual(self.kp._keystring, '')
# Valid call with ccc gets the correct count
self.kp.handle(helpers.fake_keyevent(Qt.Key_4, text='2'))
self.kp.handle(helpers.fake_keyevent(Qt.Key_2, text='3'))
self.kp.handle(helpers.fake_keyevent(Qt.Key_B, text='c'))
self.kp.handle(helpers.fake_keyevent(Qt.Key_A, text='c'))
self.kp.handle(helpers.fake_keyevent(Qt.Key_A, text='c'))
self.kp.execute.assert_called_once_with('ccc', self.kp.Type.chain, 23)
self.assertEqual(self.kp._keystring, '')
def tearDown(self):
objreg.global_registry.clear()
if __name__ == '__main__':
unittest.main()

View File

@ -1,20 +0,0 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# 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/>.
"""The qutebrowser test suite."""

View File

@ -1,84 +0,0 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# 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/>.
"""Test test helpers."""
import os
import unittest
from qutebrowser.test import helpers
class TestEnvironSetTemp(unittest.TestCase):
"""Test the environ_set_temp helper."""
def test_environ_set(self):
"""Test environ_set_temp with something which was set already."""
os.environ['QUTEBROWSER_ENVIRON_TEST'] = 'oldval'
with helpers.environ_set_temp({'QUTEBROWSER_ENVIRON_TEST': 'newval'}):
self.assertEqual(os.environ['QUTEBROWSER_ENVIRON_TEST'], 'newval')
self.assertEqual(os.environ['QUTEBROWSER_ENVIRON_TEST'], 'oldval')
def test_environ_unset(self):
"""Test environ_set_temp with something which wasn't set yet."""
with helpers.environ_set_temp({'QUTEBROWSER_ENVIRON_TEST': 'newval'}):
self.assertEqual(os.environ['QUTEBROWSER_ENVIRON_TEST'], 'newval')
self.assertNotIn('QUTEBROWSER_ENVIRON_TEST', os.environ)
def test_environ_multiple(self):
"""Test environ_set_temp with multiple values."""
os.environ['QUTEBROWSER_ENVIRON_TEST_1'] = 'oldval_1'
os.environ['QUTEBROWSER_ENVIRON_TEST_3'] = 'oldval_3'
env = {
'QUTEBROWSER_ENVIRON_TEST_1': 'newval_1',
'QUTEBROWSER_ENVIRON_TEST_2': 'newval_2',
'QUTEBROWSER_ENVIRON_TEST_3': None,
}
with helpers.environ_set_temp(env):
self.assertEqual(os.environ['QUTEBROWSER_ENVIRON_TEST_1'],
'newval_1')
self.assertEqual(os.environ['QUTEBROWSER_ENVIRON_TEST_2'],
'newval_2')
self.assertNotIn('QUTEBROWSER_ENVIRON_TEST_3', os.environ)
self.assertEqual(os.environ['QUTEBROWSER_ENVIRON_TEST_1'], 'oldval_1')
self.assertNotIn('QUTEBROWSER_ENVIRON_TEST_2', os.environ)
self.assertEqual(os.environ['QUTEBROWSER_ENVIRON_TEST_3'], 'oldval_3')
def test_environ_none_set(self):
"""Test environ_set_temp with something which was set already."""
os.environ['QUTEBROWSER_ENVIRON_TEST'] = 'oldval'
with helpers.environ_set_temp({'QUTEBROWSER_ENVIRON_TEST': None}):
self.assertNotIn('QUTEBROWSER_ENVIRON_TEST', os.environ)
self.assertEqual(os.environ['QUTEBROWSER_ENVIRON_TEST'], 'oldval')
def test_environ_none_unset(self):
"""Test environ_set_temp with something which wasn't set yet."""
with helpers.environ_set_temp({'QUTEBROWSER_ENVIRON_TEST': None}):
self.assertNotIn('QUTEBROWSER_ENVIRON_TEST', os.environ)
self.assertNotIn('QUTEBROWSER_ENVIRON_TEST', os.environ)
def tearDown(self):
if 'QUTEBROWSER_ENVIRON_TEST' in os.environ:
# if some test failed
del os.environ['QUTEBROWSER_ENVIRON_TEST']
if __name__ == '__main__':
unittest.main()

View File

@ -1,107 +0,0 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# 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/>.
"""Test test stubs."""
import unittest
from unittest import mock
from qutebrowser.test import stubs
class TestFakeTimer(unittest.TestCase):
"""Test FakeTimer."""
def setUp(self):
self.timer = stubs.FakeTimer()
def test_timeout(self):
"""Test whether timeout calls the functions."""
func = mock.Mock()
func2 = mock.Mock()
self.timer.timeout.connect(func)
self.timer.timeout.connect(func2)
self.assertFalse(func.called)
self.assertFalse(func2.called)
self.timer.timeout.emit()
func.assert_called_once_with()
func2.assert_called_once_with()
def test_disconnect_all(self):
"""Test disconnect without arguments."""
func = mock.Mock()
self.timer.timeout.connect(func)
self.timer.timeout.disconnect()
self.timer.timeout.emit()
self.assertFalse(func.called)
def test_disconnect_one(self):
"""Test disconnect with a single argument."""
func = mock.Mock()
self.timer.timeout.connect(func)
self.timer.timeout.disconnect(func)
self.timer.timeout.emit()
self.assertFalse(func.called)
def test_disconnect_all_invalid(self):
"""Test disconnecting with no connections."""
with self.assertRaises(TypeError):
self.timer.timeout.disconnect()
def test_disconnect_one_invalid(self):
"""Test disconnecting with an invalid connection."""
func1 = mock.Mock()
func2 = mock.Mock()
self.timer.timeout.connect(func1)
with self.assertRaises(TypeError):
self.timer.timeout.disconnect(func2)
self.assertFalse(func1.called)
self.assertFalse(func2.called)
self.timer.timeout.emit()
func1.assert_called_once_with()
def test_singleshot(self):
"""Test setting singleShot."""
self.assertFalse(self.timer.singleShot())
self.timer.setSingleShot(True)
self.assertTrue(self.timer.singleShot())
self.timer.start()
self.assertTrue(self.timer.isActive())
self.timer.timeout.emit()
self.assertFalse(self.timer.isActive())
def test_active(self):
"""Test isActive."""
self.assertFalse(self.timer.isActive())
self.timer.start()
self.assertTrue(self.timer.isActive())
self.timer.stop()
self.assertFalse(self.timer.isActive())
def test_interval(self):
"""Test setting an interval."""
self.assertEqual(self.timer.interval(), 0)
self.timer.setInterval(1000)
self.assertEqual(self.timer.interval(), 1000)
if __name__ == '__main__':
unittest.main()

View File

@ -1,20 +0,0 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# 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 qutebrowser.utils package."""

View File

@ -1,138 +0,0 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# 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 qutebrowser.utils.standarddir."""
import os
import os.path
import sys
import shutil
import unittest
import tempfile
from qutebrowser.utils import standarddir
from qutebrowser.test import helpers, qApp
class GetStandardDirLinuxTests(unittest.TestCase):
"""Tests for standarddir under Linux.
Attributes:
temp_dir: A temporary directory.
old_name: The old applicationName.
"""
def setUp(self):
self.temp_dir = tempfile.mkdtemp()
self.old_name = qApp.applicationName()
qApp.setApplicationName('qutebrowser')
@unittest.skipUnless(sys.platform.startswith("linux"), "requires Linux")
def test_data_explicit(self):
"""Test data dir with XDG_DATA_HOME explicitly set."""
with helpers.environ_set_temp({'XDG_DATA_HOME': self.temp_dir}):
standarddir.init(None)
expected = os.path.join(self.temp_dir, 'qutebrowser')
self.assertEqual(standarddir.data(), expected)
@unittest.skipUnless(sys.platform.startswith("linux"), "requires Linux")
def test_config_explicit(self):
"""Test config dir with XDG_CONFIG_HOME explicitly set."""
with helpers.environ_set_temp({'XDG_CONFIG_HOME': self.temp_dir}):
standarddir.init(None)
expected = os.path.join(self.temp_dir, 'qutebrowser')
self.assertEqual(standarddir.config(), expected)
@unittest.skipUnless(sys.platform.startswith("linux"), "requires Linux")
def test_cache_explicit(self):
"""Test cache dir with XDG_CACHE_HOME explicitly set."""
with helpers.environ_set_temp({'XDG_CACHE_HOME': self.temp_dir}):
standarddir.init(None)
expected = os.path.join(self.temp_dir, 'qutebrowser')
self.assertEqual(standarddir.cache(), expected)
@unittest.skipUnless(sys.platform.startswith("linux"), "requires Linux")
def test_data(self):
"""Test data dir with XDG_DATA_HOME not set."""
env = {'HOME': self.temp_dir, 'XDG_DATA_HOME': None}
with helpers.environ_set_temp(env):
standarddir.init(None)
expected = os.path.join(self.temp_dir, '.local', 'share',
'qutebrowser')
self.assertEqual(standarddir.data(), expected)
@unittest.skipUnless(sys.platform.startswith("linux"), "requires Linux")
def test_config(self):
"""Test config dir with XDG_CONFIG_HOME not set."""
env = {'HOME': self.temp_dir, 'XDG_CONFIG_HOME': None}
with helpers.environ_set_temp(env):
standarddir.init(None)
expected = os.path.join(self.temp_dir, '.config', 'qutebrowser')
self.assertEqual(standarddir.config(), expected)
@unittest.skipUnless(sys.platform.startswith("linux"), "requires Linux")
def test_cache(self):
"""Test cache dir with XDG_CACHE_HOME not set."""
env = {'HOME': self.temp_dir, 'XDG_CACHE_HOME': None}
with helpers.environ_set_temp(env):
standarddir.init(None)
expected = os.path.join(self.temp_dir, '.cache', 'qutebrowser')
self.assertEqual(standarddir.cache(), expected)
def tearDown(self):
qApp.setApplicationName(self.old_name)
shutil.rmtree(self.temp_dir)
class GetStandardDirWindowsTests(unittest.TestCase):
"""Tests for standarddir under Windows.
Attributes:
old_name: The old applicationName.
"""
def setUp(self):
self.old_name = qApp.applicationName()
# We can't store the files in a temp dir, so we don't chose qutebrowser
qApp.setApplicationName('qutebrowser_test')
standarddir.init(None)
def tearDown(self):
qApp.setApplicationName(self.old_name)
@unittest.skipUnless(sys.platform.startswith("win"), "requires Windows")
def test_data(self):
"""Test data dir."""
self.assertEqual(standarddir.data().split(os.sep)[-2:],
['qutebrowser_test', 'data'], standarddir.data())
@unittest.skipUnless(sys.platform.startswith("win"), "requires Windows")
def test_config(self):
"""Test config dir."""
self.assertEqual(standarddir.config().split(os.sep)[-1],
'qutebrowser_test',
standarddir.config())
@unittest.skipUnless(sys.platform.startswith("win"), "requires Windows")
def test_cache(self):
"""Test cache dir."""
self.assertEqual(standarddir.cache().split(os.sep)[-2:],
['qutebrowser_test', 'cache'], standarddir.cache())

View File

@ -1,20 +0,0 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# 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 qutebrowser.utils.usertype."""

View File

@ -76,7 +76,7 @@ def check_spelling(target):
# Words which look better when splitted, but might need some fine tuning. # Words which look better when splitted, but might need some fine tuning.
words |= {'keystrings', 'webelements', 'mouseevent', 'keysequence', words |= {'keystrings', 'webelements', 'mouseevent', 'keysequence',
'normalmode', 'eventloops', 'sizehint', 'statemachine', 'normalmode', 'eventloops', 'sizehint', 'statemachine',
'metaobject', 'logrecord', 'monkeypatch', 'filetype'} 'metaobject', 'logrecord', 'filetype'}
seen = collections.defaultdict(list) seen = collections.defaultdict(list)
try: try:

View File

@ -0,0 +1,60 @@
#!/usr/bin/env python3
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# 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/>.
"""Run pylint on tests.
This is needed because pylint can't check a folder which isn't a package:
https://bitbucket.org/logilab/pylint/issue/512/
"""
import os
import sys
import os.path
import subprocess
sys.path.insert(0, os.path.join(os.path.dirname(__file__), os.pardir))
from scripts import utils
def main():
"""Main entry point.
Return:
The pylint exit status.
"""
utils.change_cwd()
files = []
for dirpath, _dirnames, filenames in os.walk('tests'):
for fn in filenames:
if os.path.splitext(fn)[1] == '.py':
files.append(os.path.join(dirpath, fn))
disabled = ['attribute-defined-outside-init', 'redefined-outer-name',
'unused-argument']
no_docstring_rgx = ['^__.*__$', '^setup$']
args = (['--disable={}'.format(','.join(disabled)),
'--no-docstring-rgx=({})'.format('|'.join(no_docstring_rgx))] +
sys.argv[1:] + files)
ret = subprocess.call(['pylint'] + args)
return ret
if __name__ == '__main__':
sys.exit(main())

View File

@ -23,46 +23,39 @@ Note that tests for parse_content_disposition are in their own
test_content_disposition.py file. test_content_disposition.py file.
""" """
import unittest
from qutebrowser.browser import http from qutebrowser.browser import http
from qutebrowser.test import stubs
class ParseContentTypeTests(unittest.TestCase): class TestParseContentType:
"""Test for parse_content_type.""" """Test for parse_content_type."""
def test_not_existing(self): def test_not_existing(self, stubs):
"""Test without any Content-Type header.""" """Test without any Content-Type header."""
reply = stubs.FakeNetworkReply() reply = stubs.FakeNetworkReply()
mimetype, rest = http.parse_content_type(reply) mimetype, rest = http.parse_content_type(reply)
self.assertIsNone(mimetype) assert mimetype is None
self.assertIsNone(rest) assert rest is None
def test_mimetype(self): def test_mimetype(self, stubs):
"""Test with simple Content-Type header.""" """Test with simple Content-Type header."""
reply = stubs.FakeNetworkReply( reply = stubs.FakeNetworkReply(
headers={'Content-Type': 'image/example'}) headers={'Content-Type': 'image/example'})
mimetype, rest = http.parse_content_type(reply) mimetype, rest = http.parse_content_type(reply)
self.assertEqual(mimetype, 'image/example') assert mimetype == 'image/example'
self.assertIsNone(rest) assert rest is None
def test_empty(self): def test_empty(self, stubs):
"""Test with empty Content-Type header.""" """Test with empty Content-Type header."""
reply = stubs.FakeNetworkReply(headers={'Content-Type': ''}) reply = stubs.FakeNetworkReply(headers={'Content-Type': ''})
mimetype, rest = http.parse_content_type(reply) mimetype, rest = http.parse_content_type(reply)
self.assertEqual(mimetype, '') assert mimetype == ''
self.assertIsNone(rest) assert rest is None
def test_additional(self): def test_additional(self, stubs):
"""Test with Content-Type header with additional informations.""" """Test with Content-Type header with additional informations."""
reply = stubs.FakeNetworkReply( reply = stubs.FakeNetworkReply(
headers={'Content-Type': 'image/example; encoding=UTF-8'}) headers={'Content-Type': 'image/example; encoding=UTF-8'})
mimetype, rest = http.parse_content_type(reply) mimetype, rest = http.parse_content_type(reply)
self.assertEqual(mimetype, 'image/example') assert mimetype == 'image/example'
self.assertEqual(rest, ' encoding=UTF-8') assert rest == ' encoding=UTF-8'
if __name__ == '__main__':
unittest.main()

View File

@ -0,0 +1,139 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# 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 webelement.tabhistory."""
from PyQt5.QtCore import QUrl, QPoint
import pytest
from qutebrowser.browser import tabhistory
from qutebrowser.browser.tabhistory import TabHistoryItem as Item
from qutebrowser.utils import qtutils
class TestSerializeHistory:
"""Tests for serialize()."""
ITEMS = [
Item(QUrl('https://www.heise.de/'), QUrl('http://www.heise.de/'),
'heise'),
Item(QUrl('http://example.com/%E2%80%A6'),
QUrl('http://example.com/%E2%80%A6'), 'percent', active=True),
Item(QUrl('http://example.com/?foo=bar'),
QUrl('http://original.url.example.com/'), 'arg',
user_data={'foo': 23, 'bar': 42}),
# From https://github.com/OtterBrowser/otter-browser/issues/709#issuecomment-74749471
Item(
QUrl('http://github.com/OtterBrowser/24/134/2344/otter-browser/'
'issues/709/'),
QUrl('http://github.com/OtterBrowser/24/134/2344/otter-browser/'
'issues/709/'),
'Page not found | github',
user_data={'zoom': 149, 'scroll-pos': QPoint(0, 0)}),
Item(
QUrl('https://mail.google.com/mail/u/0/#label/some+label/'
'234lkjsd0932lkjf884jqwerdf4'),
QUrl('https://mail.google.com/mail/u/0/#label/some+label/'
'234lkjsd0932lkjf884jqwerdf4'),
'"some label" - email@gmail.com - Gmail"',
user_data={'zoom': 120, 'scroll-pos': QPoint(0, 0)}),
]
@pytest.fixture(autouse=True)
def setup(self, webpage):
self.page = webpage
self.history = self.page.history()
assert self.history.count() == 0
stream, _data, self.user_data = tabhistory.serialize(self.ITEMS)
qtutils.deserialize_stream(stream, self.history)
def test_count(self):
"""Check if the history's count was loaded correctly."""
assert self.history.count() == len(self.ITEMS)
@pytest.mark.parametrize('i', range(len(ITEMS)))
def test_valid(self, i):
"""Check if all items are valid."""
assert self.history.itemAt(i).isValid()
@pytest.mark.parametrize('i', range(len(ITEMS)))
def test_no_userdata(self, i):
"""Check if all items have no user data."""
assert self.history.itemAt(i).userData() is None
def test_userdata(self):
"""Check if all user data has been restored to self.user_data."""
userdata_items = [item.user_data for item in self.ITEMS]
assert userdata_items == self.user_data
def test_currentitem(self):
"""Check if the current item index was loaded correctly."""
assert self.history.currentItemIndex() == 1
@pytest.mark.parametrize('i, item', enumerate(ITEMS))
def test_urls(self, i, item):
"""Check if the URLs were loaded correctly."""
assert self.history.itemAt(i).url() == item.url
@pytest.mark.parametrize('i, item', enumerate(ITEMS))
def test_original_urls(self, i, item):
"""Check if the original URLs were loaded correctly."""
assert self.history.itemAt(i).originalUrl() == item.original_url
@pytest.mark.parametrize('i, item', enumerate(ITEMS))
def test_titles(self, i, item):
"""Check if the titles were loaded correctly."""
assert self.history.itemAt(i).title() == item.title
class TestSerializeHistorySpecial:
"""Tests for serialize() without items set up in setup."""
@pytest.fixture(autouse=True)
def setup(self, webpage):
"""Set up the initial QWebPage for each test."""
self.page = webpage
self.history = self.page.history()
assert self.history.count() == 0
def test_no_active_item(self):
"""Check tabhistory.serialize with no active item."""
items = [Item(QUrl(), QUrl(), '')]
with pytest.raises(ValueError):
tabhistory.serialize(items)
def test_two_active_items(self):
"""Check tabhistory.serialize with two active items."""
items = [Item(QUrl(), QUrl(), '', active=True),
Item(QUrl(), QUrl(), ''),
Item(QUrl(), QUrl(), '', active=True)]
with pytest.raises(ValueError):
tabhistory.serialize(items)
def test_empty(self):
"""Check tabhistory.serialize with no items."""
items = []
stream, _data, user_data = tabhistory.serialize(items)
qtutils.deserialize_stream(stream, self.history)
assert self.history.count() == 0
assert self.history.currentItemIndex() == 0
assert not user_data

View File

@ -21,15 +21,14 @@
"""Tests for the webelement utils.""" """Tests for the webelement utils."""
import unittest
from unittest import mock from unittest import mock
import collections.abc import collections.abc
from PyQt5.QtCore import QRect, QPoint from PyQt5.QtCore import QRect, QPoint
from PyQt5.QtWebKit import QWebElement from PyQt5.QtWebKit import QWebElement
import pytest
from qutebrowser.browser import webelem from qutebrowser.browser import webelem
from qutebrowser.test import stubs
def get_webelem(geometry=None, frame=None, null=False, visibility='', def get_webelem(geometry=None, frame=None, null=False, visibility='',
@ -87,17 +86,17 @@ def get_webelem(geometry=None, frame=None, null=False, visibility='',
return wrapped return wrapped
class WebElementWrapperTests(unittest.TestCase): class TestWebElementWrapper:
"""Test WebElementWrapper.""" """Test WebElementWrapper."""
def test_nullelem(self): def test_nullelem(self):
"""Test __init__ with a null element.""" """Test __init__ with a null element."""
with self.assertRaises(webelem.IsNullError): with pytest.raises(webelem.IsNullError):
get_webelem(null=True) get_webelem(null=True)
class IsVisibleInvalidTests(unittest.TestCase): class TestIsVisibleInvalid:
"""Tests for is_visible with invalid elements. """Tests for is_visible with invalid elements.
@ -105,7 +104,8 @@ class IsVisibleInvalidTests(unittest.TestCase):
frame: The FakeWebFrame we're using to test. frame: The FakeWebFrame we're using to test.
""" """
def setUp(self): @pytest.fixture(autouse=True)
def setup(self, stubs):
self.frame = stubs.FakeWebFrame(QRect(0, 0, 100, 100)) self.frame = stubs.FakeWebFrame(QRect(0, 0, 100, 100))
def test_nullelem(self): def test_nullelem(self):
@ -116,15 +116,15 @@ class IsVisibleInvalidTests(unittest.TestCase):
""" """
elem = get_webelem() elem = get_webelem()
elem._elem.isNull.return_value = True elem._elem.isNull.return_value = True
with self.assertRaises(webelem.IsNullError): with pytest.raises(webelem.IsNullError):
elem.is_visible(self.frame) elem.is_visible(self.frame)
def test_invalid_invisible(self): def test_invalid_invisible(self):
"""Test elements with an invalid geometry which are invisible.""" """Test elements with an invalid geometry which are invisible."""
elem = get_webelem(QRect(0, 0, 0, 0), self.frame) elem = get_webelem(QRect(0, 0, 0, 0), self.frame)
self.assertFalse(elem.geometry().isValid()) assert not elem.geometry().isValid()
self.assertEqual(elem.geometry().x(), 0) assert elem.geometry().x() == 0
self.assertFalse(elem.is_visible(self.frame)) assert not elem.is_visible(self.frame)
def test_invalid_visible(self): def test_invalid_visible(self):
"""Test elements with an invalid geometry which are visible. """Test elements with an invalid geometry which are visible.
@ -133,11 +133,11 @@ class IsVisibleInvalidTests(unittest.TestCase):
which *are* visible, but don't have a valid geometry. which *are* visible, but don't have a valid geometry.
""" """
elem = get_webelem(QRect(10, 10, 0, 0), self.frame) elem = get_webelem(QRect(10, 10, 0, 0), self.frame)
self.assertFalse(elem.geometry().isValid()) assert not elem.geometry().isValid()
self.assertTrue(elem.is_visible(self.frame)) assert elem.is_visible(self.frame)
class IsVisibleScrollTests(unittest.TestCase): class TestIsVisibleScroll:
"""Tests for is_visible when the frame is scrolled. """Tests for is_visible when the frame is scrolled.
@ -145,22 +145,23 @@ class IsVisibleScrollTests(unittest.TestCase):
frame: The FakeWebFrame we're using to test. frame: The FakeWebFrame we're using to test.
""" """
def setUp(self): @pytest.fixture(autouse=True)
def setup(self, stubs):
self.frame = stubs.FakeWebFrame(QRect(0, 0, 100, 100), self.frame = stubs.FakeWebFrame(QRect(0, 0, 100, 100),
scroll=QPoint(10, 10)) scroll=QPoint(10, 10))
def test_invisible(self): def test_invisible(self):
"""Test elements which should be invisible due to scrolling.""" """Test elements which should be invisible due to scrolling."""
elem = get_webelem(QRect(5, 5, 4, 4), self.frame) elem = get_webelem(QRect(5, 5, 4, 4), self.frame)
self.assertFalse(elem.is_visible(self.frame)) assert not elem.is_visible(self.frame)
def test_visible(self): def test_visible(self):
"""Test elements which still should be visible after scrolling.""" """Test elements which still should be visible after scrolling."""
elem = get_webelem(QRect(10, 10, 1, 1), self.frame) elem = get_webelem(QRect(10, 10, 1, 1), self.frame)
self.assertTrue(elem.is_visible(self.frame)) assert elem.is_visible(self.frame)
class IsVisibleCssTests(unittest.TestCase): class TestIsVisibleCss:
"""Tests for is_visible with CSS attributes. """Tests for is_visible with CSS attributes.
@ -168,33 +169,34 @@ class IsVisibleCssTests(unittest.TestCase):
frame: The FakeWebFrame we're using to test. frame: The FakeWebFrame we're using to test.
""" """
def setUp(self): @pytest.fixture(autouse=True)
def setup(self, stubs):
self.frame = stubs.FakeWebFrame(QRect(0, 0, 100, 100)) self.frame = stubs.FakeWebFrame(QRect(0, 0, 100, 100))
def test_visibility_visible(self): def test_visibility_visible(self):
"""Check that elements with "visibility = visible" are visible.""" """Check that elements with "visibility = visible" are visible."""
elem = get_webelem(QRect(0, 0, 10, 10), self.frame, elem = get_webelem(QRect(0, 0, 10, 10), self.frame,
visibility='visible') visibility='visible')
self.assertTrue(elem.is_visible(self.frame)) assert elem.is_visible(self.frame)
def test_visibility_hidden(self): def test_visibility_hidden(self):
"""Check that elements with "visibility = hidden" are not visible.""" """Check that elements with "visibility = hidden" are not visible."""
elem = get_webelem(QRect(0, 0, 10, 10), self.frame, elem = get_webelem(QRect(0, 0, 10, 10), self.frame,
visibility='hidden') visibility='hidden')
self.assertFalse(elem.is_visible(self.frame)) assert not elem.is_visible(self.frame)
def test_display_inline(self): def test_display_inline(self):
"""Check that elements with "display = inline" are visible.""" """Check that elements with "display = inline" are visible."""
elem = get_webelem(QRect(0, 0, 10, 10), self.frame, display='inline') elem = get_webelem(QRect(0, 0, 10, 10), self.frame, display='inline')
self.assertTrue(elem.is_visible(self.frame)) assert elem.is_visible(self.frame)
def test_display_none(self): def test_display_none(self):
"""Check that elements with "display = none" are not visible.""" """Check that elements with "display = none" are not visible."""
elem = get_webelem(QRect(0, 0, 10, 10), self.frame, display='none') elem = get_webelem(QRect(0, 0, 10, 10), self.frame, display='none')
self.assertFalse(elem.is_visible(self.frame)) assert not elem.is_visible(self.frame)
class IsVisibleIframeTests(unittest.TestCase): class TestIsVisibleIframe:
"""Tests for is_visible with a child frame. """Tests for is_visible with a child frame.
@ -204,7 +206,8 @@ class IsVisibleIframeTests(unittest.TestCase):
elem1-elem4: FakeWebElements to test. elem1-elem4: FakeWebElements to test.
""" """
def setUp(self): @pytest.fixture(autouse=True)
def setup(self, stubs):
"""Set up the following base situation. """Set up the following base situation.
0, 0 300, 0 0, 0 300, 0
@ -236,64 +239,64 @@ class IsVisibleIframeTests(unittest.TestCase):
def test_not_scrolled(self): def test_not_scrolled(self):
"""Test base situation.""" """Test base situation."""
self.assertTrue(self.frame.geometry().contains(self.iframe.geometry())) assert self.frame.geometry().contains(self.iframe.geometry())
self.assertTrue(self.elem1.is_visible(self.frame)) assert self.elem1.is_visible(self.frame)
self.assertTrue(self.elem2.is_visible(self.frame)) assert self.elem2.is_visible(self.frame)
self.assertFalse(self.elem3.is_visible(self.frame)) assert not self.elem3.is_visible(self.frame)
self.assertTrue(self.elem4.is_visible(self.frame)) assert self.elem4.is_visible(self.frame)
def test_iframe_scrolled(self): def test_iframe_scrolled(self):
"""Scroll iframe down so elem3 gets visible and elem1/elem2 not.""" """Scroll iframe down so elem3 gets visible and elem1/elem2 not."""
self.iframe.scrollPosition.return_value = QPoint(0, 100) self.iframe.scrollPosition.return_value = QPoint(0, 100)
self.assertFalse(self.elem1.is_visible(self.frame)) assert not self.elem1.is_visible(self.frame)
self.assertFalse(self.elem2.is_visible(self.frame)) assert not self.elem2.is_visible(self.frame)
self.assertTrue(self.elem3.is_visible(self.frame)) assert self.elem3.is_visible(self.frame)
self.assertTrue(self.elem4.is_visible(self.frame)) assert self.elem4.is_visible(self.frame)
def test_mainframe_scrolled_iframe_visible(self): def test_mainframe_scrolled_iframe_visible(self):
"""Scroll mainframe down so iframe is partly visible but elem1 not.""" """Scroll mainframe down so iframe is partly visible but elem1 not."""
self.frame.scrollPosition.return_value = QPoint(0, 50) self.frame.scrollPosition.return_value = QPoint(0, 50)
geom = self.frame.geometry().translated(self.frame.scrollPosition()) geom = self.frame.geometry().translated(self.frame.scrollPosition())
self.assertFalse(geom.contains(self.iframe.geometry())) assert not geom.contains(self.iframe.geometry())
self.assertTrue(geom.intersects(self.iframe.geometry())) assert geom.intersects(self.iframe.geometry())
self.assertFalse(self.elem1.is_visible(self.frame)) assert not self.elem1.is_visible(self.frame)
self.assertTrue(self.elem2.is_visible(self.frame)) assert self.elem2.is_visible(self.frame)
self.assertFalse(self.elem3.is_visible(self.frame)) assert not self.elem3.is_visible(self.frame)
self.assertTrue(self.elem4.is_visible(self.frame)) assert self.elem4.is_visible(self.frame)
def test_mainframe_scrolled_iframe_invisible(self): def test_mainframe_scrolled_iframe_invisible(self):
"""Scroll mainframe down so iframe is invisible.""" """Scroll mainframe down so iframe is invisible."""
self.frame.scrollPosition.return_value = QPoint(0, 110) self.frame.scrollPosition.return_value = QPoint(0, 110)
geom = self.frame.geometry().translated(self.frame.scrollPosition()) geom = self.frame.geometry().translated(self.frame.scrollPosition())
self.assertFalse(geom.contains(self.iframe.geometry())) assert not geom.contains(self.iframe.geometry())
self.assertFalse(geom.intersects(self.iframe.geometry())) assert not geom.intersects(self.iframe.geometry())
self.assertFalse(self.elem1.is_visible(self.frame)) assert not self.elem1.is_visible(self.frame)
self.assertFalse(self.elem2.is_visible(self.frame)) assert not self.elem2.is_visible(self.frame)
self.assertFalse(self.elem3.is_visible(self.frame)) assert not self.elem3.is_visible(self.frame)
self.assertTrue(self.elem4.is_visible(self.frame)) assert self.elem4.is_visible(self.frame)
class IsWritableTests(unittest.TestCase): class TestIsWritable:
"""Check is_writable.""" """Check is_writable."""
def test_writable(self): def test_writable(self):
"""Test a normal element.""" """Test a normal element."""
elem = get_webelem() elem = get_webelem()
self.assertTrue(elem.is_writable()) assert elem.is_writable()
def test_disabled(self): def test_disabled(self):
"""Test a disabled element.""" """Test a disabled element."""
elem = get_webelem(attributes=['disabled']) elem = get_webelem(attributes=['disabled'])
self.assertFalse(elem.is_writable()) assert not elem.is_writable()
def test_readonly(self): def test_readonly(self):
"""Test a readonly element.""" """Test a readonly element."""
elem = get_webelem(attributes=['readonly']) elem = get_webelem(attributes=['readonly'])
self.assertFalse(elem.is_writable()) assert not elem.is_writable()
class JavascriptEscapeTests(unittest.TestCase): class TestJavascriptEscape:
"""Check javascript_escape. """Check javascript_escape.
@ -301,33 +304,30 @@ class JavascriptEscapeTests(unittest.TestCase):
STRINGS: A list of (input, output) tuples. STRINGS: A list of (input, output) tuples.
""" """
STRINGS = ( @pytest.mark.parametrize('before, after', [
('foo\\bar', r'foo\\bar'), ('foo\\bar', r'foo\\bar'),
('foo\nbar', r'foo\nbar'), ('foo\nbar', r'foo\nbar'),
("foo'bar", r"foo\'bar"), ("foo'bar", r"foo\'bar"),
('foo"bar', r'foo\"bar'), ('foo"bar', r'foo\"bar'),
) ])
def test_fake_escape(self, before, after):
def test_fake_escape(self):
"""Test javascript escaping.""" """Test javascript escaping."""
for before, after in self.STRINGS: assert webelem.javascript_escape(before) == after
with self.subTest(before=before):
self.assertEqual(webelem.javascript_escape(before), after)
class GetChildFramesTests(unittest.TestCase): class TestGetChildFrames:
"""Check get_child_frames.""" """Check get_child_frames."""
def test_single_frame(self): def test_single_frame(self, stubs):
"""Test get_child_frames with a single frame without children.""" """Test get_child_frames with a single frame without children."""
frame = stubs.FakeChildrenFrame() frame = stubs.FakeChildrenFrame()
children = webelem.get_child_frames(frame) children = webelem.get_child_frames(frame)
self.assertEqual(len(children), 1) assert len(children) == 1
self.assertIs(children[0], frame) assert children[0] is frame
frame.childFrames.assert_called_once_with() frame.childFrames.assert_called_once_with()
def test_one_level(self): def test_one_level(self, stubs):
r"""Test get_child_frames with one level of children. r"""Test get_child_frames with one level of children.
o parent o parent
@ -338,15 +338,15 @@ class GetChildFramesTests(unittest.TestCase):
child2 = stubs.FakeChildrenFrame() child2 = stubs.FakeChildrenFrame()
parent = stubs.FakeChildrenFrame([child1, child2]) parent = stubs.FakeChildrenFrame([child1, child2])
children = webelem.get_child_frames(parent) children = webelem.get_child_frames(parent)
self.assertEqual(len(children), 3) assert len(children) == 3
self.assertIs(children[0], parent) assert children[0] is parent
self.assertIs(children[1], child1) assert children[1] is child1
self.assertIs(children[2], child2) assert children[2] is child2
parent.childFrames.assert_called_once_with() parent.childFrames.assert_called_once_with()
child1.childFrames.assert_called_once_with() child1.childFrames.assert_called_once_with()
child2.childFrames.assert_called_once_with() child2.childFrames.assert_called_once_with()
def test_multiple_levels(self): def test_multiple_levels(self, stubs):
r"""Test get_child_frames with multiple levels of children. r"""Test get_child_frames with multiple levels of children.
o root o root
@ -360,189 +360,191 @@ class GetChildFramesTests(unittest.TestCase):
stubs.FakeChildrenFrame(second[2:4])] stubs.FakeChildrenFrame(second[2:4])]
root = stubs.FakeChildrenFrame(first) root = stubs.FakeChildrenFrame(first)
children = webelem.get_child_frames(root) children = webelem.get_child_frames(root)
self.assertEqual(len(children), 7) assert len(children) == 7
self.assertIs(children[0], root) assert children[0] is root
for frame in [root] + first + second: for frame in [root] + first + second:
with self.subTest(frame=frame): frame.childFrames.assert_called_once_with()
frame.childFrames.assert_called_once_with()
class IsEditableTests(unittest.TestCase): class TestIsEditable:
"""Tests for is_editable.""" """Tests for is_editable."""
def setUp(self): @pytest.yield_fixture(autouse=True)
def setup(self):
old_config = webelem.config
webelem.config = None webelem.config = None
yield
webelem.config = old_config
@pytest.fixture
def stub_config(self, stubs, mocker):
"""Fixture to create a config stub with an input section."""
config = stubs.ConfigStub({'input': {}})
mocker.patch('qutebrowser.browser.webelem.config', new=config)
return config
def test_input_plain(self): def test_input_plain(self):
"""Test with plain input element.""" """Test with plain input element."""
elem = get_webelem(tagname='input') elem = get_webelem(tagname='input')
self.assertTrue(elem.is_editable()) assert elem.is_editable()
def test_input_text(self): def test_input_text(self):
"""Test with text input element.""" """Test with text input element."""
elem = get_webelem(tagname='input', attributes={'type': 'text'}) elem = get_webelem(tagname='input', attributes={'type': 'text'})
self.assertTrue(elem.is_editable()) assert elem.is_editable()
def test_input_text_caps(self): def test_input_text_caps(self):
"""Test with text input element with caps attributes.""" """Test with text input element with caps attributes."""
elem = get_webelem(tagname='INPUT', attributes={'TYPE': 'TEXT'}) elem = get_webelem(tagname='INPUT', attributes={'TYPE': 'TEXT'})
self.assertTrue(elem.is_editable()) assert elem.is_editable()
def test_input_email(self): def test_input_email(self):
"""Test with email input element.""" """Test with email input element."""
elem = get_webelem(tagname='input', attributes={'type': 'email'}) elem = get_webelem(tagname='input', attributes={'type': 'email'})
self.assertTrue(elem.is_editable()) assert elem.is_editable()
def test_input_url(self): def test_input_url(self):
"""Test with url input element.""" """Test with url input element."""
elem = get_webelem(tagname='input', attributes={'type': 'url'}) elem = get_webelem(tagname='input', attributes={'type': 'url'})
self.assertTrue(elem.is_editable()) assert elem.is_editable()
def test_input_tel(self): def test_input_tel(self):
"""Test with tel input element.""" """Test with tel input element."""
elem = get_webelem(tagname='input', attributes={'type': 'tel'}) elem = get_webelem(tagname='input', attributes={'type': 'tel'})
self.assertTrue(elem.is_editable()) assert elem.is_editable()
def test_input_number(self): def test_input_number(self):
"""Test with number input element.""" """Test with number input element."""
elem = get_webelem(tagname='input', attributes={'type': 'number'}) elem = get_webelem(tagname='input', attributes={'type': 'number'})
self.assertTrue(elem.is_editable()) assert elem.is_editable()
def test_input_password(self): def test_input_password(self):
"""Test with password input element.""" """Test with password input element."""
elem = get_webelem(tagname='input', attributes={'type': 'password'}) elem = get_webelem(tagname='input', attributes={'type': 'password'})
self.assertTrue(elem.is_editable()) assert elem.is_editable()
def test_input_search(self): def test_input_search(self):
"""Test with search input element.""" """Test with search input element."""
elem = get_webelem(tagname='input', attributes={'type': 'search'}) elem = get_webelem(tagname='input', attributes={'type': 'search'})
self.assertTrue(elem.is_editable()) assert elem.is_editable()
def test_input_button(self): def test_input_button(self):
"""Button should not be editable.""" """Button should not be editable."""
elem = get_webelem(tagname='input', attributes={'type': 'button'}) elem = get_webelem(tagname='input', attributes={'type': 'button'})
self.assertFalse(elem.is_editable()) assert not elem.is_editable()
def test_input_checkbox(self): def test_input_checkbox(self):
"""Checkbox should not be editable.""" """Checkbox should not be editable."""
elem = get_webelem(tagname='input', attributes={'type': 'checkbox'}) elem = get_webelem(tagname='input', attributes={'type': 'checkbox'})
self.assertFalse(elem.is_editable()) assert not elem.is_editable()
def test_textarea(self): def test_textarea(self):
"""Test textarea element.""" """Test textarea element."""
elem = get_webelem(tagname='textarea') elem = get_webelem(tagname='textarea')
self.assertTrue(elem.is_editable()) assert elem.is_editable()
def test_select(self): def test_select(self):
"""Test selectbox.""" """Test selectbox."""
elem = get_webelem(tagname='select') elem = get_webelem(tagname='select')
self.assertFalse(elem.is_editable()) assert not elem.is_editable()
def test_input_disabled(self): def test_input_disabled(self):
"""Test disabled input element.""" """Test disabled input element."""
elem = get_webelem(tagname='input', attributes={'disabled': None}) elem = get_webelem(tagname='input', attributes={'disabled': None})
self.assertFalse(elem.is_editable()) assert not elem.is_editable()
def test_input_readonly(self): def test_input_readonly(self):
"""Test readonly input element.""" """Test readonly input element."""
elem = get_webelem(tagname='input', attributes={'readonly': None}) elem = get_webelem(tagname='input', attributes={'readonly': None})
self.assertFalse(elem.is_editable()) assert not elem.is_editable()
def test_textarea_disabled(self): def test_textarea_disabled(self):
"""Test disabled textarea element.""" """Test disabled textarea element."""
elem = get_webelem(tagname='textarea', attributes={'disabled': None}) elem = get_webelem(tagname='textarea', attributes={'disabled': None})
self.assertFalse(elem.is_editable()) assert not elem.is_editable()
def test_textarea_readonly(self): def test_textarea_readonly(self):
"""Test readonly textarea element.""" """Test readonly textarea element."""
elem = get_webelem(tagname='textarea', attributes={'readonly': None}) elem = get_webelem(tagname='textarea', attributes={'readonly': None})
self.assertFalse(elem.is_editable()) assert not elem.is_editable()
@mock.patch('qutebrowser.browser.webelem.config', new=stubs.ConfigStub( def test_embed_true(self, stub_config):
{'input': {'insert-mode-on-plugins': True}}))
def test_embed_true(self):
"""Test embed-element with insert-mode-on-plugins true.""" """Test embed-element with insert-mode-on-plugins true."""
stub_config.data['input']['insert-mode-on-plugins'] = True
elem = get_webelem(tagname='embed') elem = get_webelem(tagname='embed')
self.assertTrue(elem.is_editable()) assert elem.is_editable()
@mock.patch('qutebrowser.browser.webelem.config', new=stubs.ConfigStub( def test_applet_true(self, stub_config):
{'input': {'insert-mode-on-plugins': True}}))
def test_applet_true(self):
"""Test applet-element with insert-mode-on-plugins true.""" """Test applet-element with insert-mode-on-plugins true."""
stub_config.data['input']['insert-mode-on-plugins'] = True
elem = get_webelem(tagname='applet') elem = get_webelem(tagname='applet')
self.assertTrue(elem.is_editable()) assert elem.is_editable()
@mock.patch('qutebrowser.browser.webelem.config', new=stubs.ConfigStub( def test_embed_false(self, stub_config):
{'input': {'insert-mode-on-plugins': False}}))
def test_embed_false(self):
"""Test embed-element with insert-mode-on-plugins false.""" """Test embed-element with insert-mode-on-plugins false."""
stub_config.data['input']['insert-mode-on-plugins'] = False
elem = get_webelem(tagname='embed') elem = get_webelem(tagname='embed')
self.assertFalse(elem.is_editable()) assert not elem.is_editable()
@mock.patch('qutebrowser.browser.webelem.config', new=stubs.ConfigStub( def test_applet_false(self, stub_config):
{'input': {'insert-mode-on-plugins': False}}))
def test_applet_false(self):
"""Test applet-element with insert-mode-on-plugins false.""" """Test applet-element with insert-mode-on-plugins false."""
stub_config.data['input']['insert-mode-on-plugins'] = False
elem = get_webelem(tagname='applet') elem = get_webelem(tagname='applet')
self.assertFalse(elem.is_editable()) assert not elem.is_editable()
def test_object_no_type(self): def test_object_no_type(self):
"""Test object-element without type.""" """Test object-element without type."""
elem = get_webelem(tagname='object') elem = get_webelem(tagname='object')
self.assertFalse(elem.is_editable()) assert not elem.is_editable()
def test_object_image(self): def test_object_image(self):
"""Test object-element with image type.""" """Test object-element with image type."""
elem = get_webelem(tagname='object', attributes={'type': 'image/gif'}) elem = get_webelem(tagname='object', attributes={'type': 'image/gif'})
self.assertFalse(elem.is_editable()) assert not elem.is_editable()
@mock.patch('qutebrowser.browser.webelem.config', new=stubs.ConfigStub( def test_object_application(self, stub_config):
{'input': {'insert-mode-on-plugins': True}}))
def test_object_application(self):
"""Test object-element with application type.""" """Test object-element with application type."""
stub_config.data['input']['insert-mode-on-plugins'] = True
elem = get_webelem(tagname='object', elem = get_webelem(tagname='object',
attributes={'type': 'application/foo'}) attributes={'type': 'application/foo'})
self.assertTrue(elem.is_editable()) assert elem.is_editable()
@mock.patch('qutebrowser.browser.webelem.config', new=stubs.ConfigStub( def test_object_application_false(self, stub_config):
{'input': {'insert-mode-on-plugins': False}}))
def test_object_application_false(self):
"""Test object-element with application type but not ...-on-plugins.""" """Test object-element with application type but not ...-on-plugins."""
stub_config.data['input']['insert-mode-on-plugins'] = False
elem = get_webelem(tagname='object', elem = get_webelem(tagname='object',
attributes={'type': 'application/foo'}) attributes={'type': 'application/foo'})
self.assertFalse(elem.is_editable()) assert not elem.is_editable()
@mock.patch('qutebrowser.browser.webelem.config', new=stubs.ConfigStub( def test_object_classid(self, stub_config):
{'input': {'insert-mode-on-plugins': True}}))
def test_object_classid(self):
"""Test object-element with classid.""" """Test object-element with classid."""
stub_config.data['input']['insert-mode-on-plugins'] = True
elem = get_webelem(tagname='object', elem = get_webelem(tagname='object',
attributes={'type': 'foo', 'classid': 'foo'}) attributes={'type': 'foo', 'classid': 'foo'})
self.assertTrue(elem.is_editable()) assert elem.is_editable()
@mock.patch('qutebrowser.browser.webelem.config', new=stubs.ConfigStub( def test_object_classid_false(self, stub_config):
{'input': {'insert-mode-on-plugins': False}}))
def test_object_classid_false(self):
"""Test object-element with classid but not insert-mode-on-plugins.""" """Test object-element with classid but not insert-mode-on-plugins."""
stub_config.data['input']['insert-mode-on-plugins'] = False
elem = get_webelem(tagname='object', elem = get_webelem(tagname='object',
attributes={'type': 'foo', 'classid': 'foo'}) attributes={'type': 'foo', 'classid': 'foo'})
self.assertFalse(elem.is_editable()) assert not elem.is_editable()
def test_div_empty(self): def test_div_empty(self):
"""Test div-element without class.""" """Test div-element without class."""
elem = get_webelem(tagname='div') elem = get_webelem(tagname='div')
self.assertFalse(elem.is_editable()) assert not elem.is_editable()
def test_div_noneditable(self): def test_div_noneditable(self):
"""Test div-element with non-editable class.""" """Test div-element with non-editable class."""
elem = get_webelem(tagname='div', classes='foo-kix-bar') elem = get_webelem(tagname='div', classes='foo-kix-bar')
self.assertFalse(elem.is_editable()) assert not elem.is_editable()
def test_div_xik(self): def test_div_xik(self):
"""Test div-element with xik class.""" """Test div-element with xik class."""
elem = get_webelem(tagname='div', classes='foo kix-foo') elem = get_webelem(tagname='div', classes='foo kix-foo')
self.assertTrue(elem.is_editable()) assert elem.is_editable()
def test_div_xik_caps(self): def test_div_xik_caps(self):
"""Test div-element with xik class in caps. """Test div-element with xik class in caps.
@ -550,13 +552,9 @@ class IsEditableTests(unittest.TestCase):
This tests if classes are case sensitive as they should. This tests if classes are case sensitive as they should.
""" """
elem = get_webelem(tagname='div', classes='KIX-FOO') elem = get_webelem(tagname='div', classes='KIX-FOO')
self.assertFalse(elem.is_editable()) assert not elem.is_editable()
def test_div_codemirror(self): def test_div_codemirror(self):
"""Test div-element with codemirror class.""" """Test div-element with codemirror class."""
elem = get_webelem(tagname='div', classes='foo CodeMirror-foo') elem = get_webelem(tagname='div', classes='foo CodeMirror-foo')
self.assertTrue(elem.is_editable()) assert elem.is_editable()
if __name__ == '__main__':
unittest.main()

View File

@ -22,27 +22,25 @@
import os import os
import os.path import os.path
import unittest
import configparser import configparser
import tempfile
import types import types
import shutil
import argparse import argparse
from unittest import mock from unittest import mock
from PyQt5.QtCore import QObject from PyQt5.QtCore import QObject
from PyQt5.QtGui import QColor from PyQt5.QtGui import QColor
import pytest
from qutebrowser.config import config, configexc from qutebrowser.config import config, configexc
from qutebrowser.test import helpers
from qutebrowser.utils import objreg, standarddir from qutebrowser.utils import objreg, standarddir
class ConfigParserTests(unittest.TestCase): class TestConfigParser:
"""Test reading of ConfigParser.""" """Test reading of ConfigParser."""
def setUp(self): @pytest.fixture(autouse=True)
def setup(self):
self.cp = configparser.ConfigParser(interpolation=None, self.cp = configparser.ConfigParser(interpolation=None,
comment_prefixes='#') comment_prefixes='#')
self.cp.optionxform = lambda opt: opt # be case-insensitive self.cp.optionxform = lambda opt: opt # be case-insensitive
@ -52,43 +50,41 @@ class ConfigParserTests(unittest.TestCase):
"""Test a simple option which is not transformed.""" """Test a simple option which is not transformed."""
self.cp.read_dict({'general': {'ignore-case': 'false'}}) self.cp.read_dict({'general': {'ignore-case': 'false'}})
self.cfg._from_cp(self.cp) self.cfg._from_cp(self.cp)
self.assertFalse(self.cfg.get('general', 'ignore-case')) assert not self.cfg.get('general', 'ignore-case')
def test_transformed_section_old(self): def test_transformed_section_old(self):
"""Test a transformed section with the old name.""" """Test a transformed section with the old name."""
self.cp.read_dict({'permissions': {'allow-plugins': 'true'}}) self.cp.read_dict({'permissions': {'allow-plugins': 'true'}})
self.cfg._from_cp(self.cp) self.cfg._from_cp(self.cp)
self.assertTrue(self.cfg.get('content', 'allow-plugins')) assert self.cfg.get('content', 'allow-plugins')
def test_transformed_section_new(self): def test_transformed_section_new(self):
"""Test a transformed section with the new name.""" """Test a transformed section with the new name."""
self.cp.read_dict({'content': {'allow-plugins': 'true'}}) self.cp.read_dict({'content': {'allow-plugins': 'true'}})
self.cfg._from_cp(self.cp) self.cfg._from_cp(self.cp)
self.assertTrue(self.cfg.get('content', 'allow-plugins')) assert self.cfg.get('content', 'allow-plugins')
def test_transformed_option_old(self): def test_transformed_option_old(self):
"""Test a transformed option with the old name.""" """Test a transformed option with the old name."""
# WORKAROUND for unknown PyQt bug
# Instance of 'str' has no 'name' member
self.cp.read_dict({'colors': {'tab.fg.odd': 'pink'}}) self.cp.read_dict({'colors': {'tab.fg.odd': 'pink'}})
self.cfg._from_cp(self.cp) self.cfg._from_cp(self.cp)
self.assertEqual(self.cfg.get('colors', 'tabs.fg.odd').name(), actual = self.cfg.get('colors', 'tabs.fg.odd').name()
QColor('pink').name()) expected = QColor('pink').name()
assert actual == expected
def test_transformed_option_new(self): def test_transformed_option_new(self):
"""Test a transformed section with the new name.""" """Test a transformed section with the new name."""
# WORKAROUND for unknown PyQt bug
# Instance of 'str' has no 'name' member
self.cp.read_dict({'colors': {'tabs.fg.odd': 'pink'}}) self.cp.read_dict({'colors': {'tabs.fg.odd': 'pink'}})
self.cfg._from_cp(self.cp) self.cfg._from_cp(self.cp)
self.assertEqual(self.cfg.get('colors', 'tabs.fg.odd').name(), actual = self.cfg.get('colors', 'tabs.fg.odd').name()
QColor('pink').name()) expected = QColor('pink').name()
assert actual == expected
def test_invalid_value(self): def test_invalid_value(self):
"""Test setting an invalid value.""" """Test setting an invalid value."""
self.cp.read_dict({'general': {'ignore-case': 'invalid'}}) self.cp.read_dict({'general': {'ignore-case': 'invalid'}})
self.cfg._from_cp(self.cp) self.cfg._from_cp(self.cp)
with self.assertRaises(configexc.ValidationError): with pytest.raises(configexc.ValidationError):
self.cfg._validate_all() self.cfg._validate_all()
def test_invalid_value_interpolated(self): def test_invalid_value_interpolated(self):
@ -96,7 +92,7 @@ class ConfigParserTests(unittest.TestCase):
self.cp.read_dict({'general': {'ignore-case': 'smart', self.cp.read_dict({'general': {'ignore-case': 'smart',
'wrap-search': '${ignore-case}'}}) 'wrap-search': '${ignore-case}'}})
self.cfg._from_cp(self.cp) self.cfg._from_cp(self.cp)
with self.assertRaises(configexc.ValidationError): with pytest.raises(configexc.ValidationError):
self.cfg._validate_all() self.cfg._validate_all()
def test_interpolation(self): def test_interpolation(self):
@ -104,8 +100,8 @@ class ConfigParserTests(unittest.TestCase):
self.cp.read_dict({'general': {'ignore-case': 'false', self.cp.read_dict({'general': {'ignore-case': 'false',
'wrap-search': '${ignore-case}'}}) 'wrap-search': '${ignore-case}'}})
self.cfg._from_cp(self.cp) self.cfg._from_cp(self.cp)
self.assertFalse(self.cfg.get('general', 'ignore-case')) assert not self.cfg.get('general', 'ignore-case')
self.assertFalse(self.cfg.get('general', 'wrap-search')) assert not self.cfg.get('general', 'wrap-search')
def test_interpolation_cross_section(self): def test_interpolation_cross_section(self):
"""Test setting an interpolated value from another section.""" """Test setting an interpolated value from another section."""
@ -116,50 +112,50 @@ class ConfigParserTests(unittest.TestCase):
} }
) )
self.cfg._from_cp(self.cp) self.cfg._from_cp(self.cp)
self.assertFalse(self.cfg.get('general', 'ignore-case')) assert not self.cfg.get('general', 'ignore-case')
self.assertFalse(self.cfg.get('network', 'do-not-track')) assert not self.cfg.get('network', 'do-not-track')
def test_invalid_interpolation(self): def test_invalid_interpolation(self):
"""Test an invalid interpolation.""" """Test an invalid interpolation."""
self.cp.read_dict({'general': {'ignore-case': '${foo}'}}) self.cp.read_dict({'general': {'ignore-case': '${foo}'}})
self.cfg._from_cp(self.cp) self.cfg._from_cp(self.cp)
with self.assertRaises(configparser.InterpolationError): with pytest.raises(configparser.InterpolationError):
self.cfg._validate_all() self.cfg._validate_all()
def test_invalid_interpolation_syntax(self): def test_invalid_interpolation_syntax(self):
"""Test an invalid interpolation syntax.""" """Test an invalid interpolation syntax."""
self.cp.read_dict({'general': {'ignore-case': '${'}}) self.cp.read_dict({'general': {'ignore-case': '${'}})
with self.assertRaises(configexc.InterpolationSyntaxError): with pytest.raises(configexc.InterpolationSyntaxError):
self.cfg._from_cp(self.cp) self.cfg._from_cp(self.cp)
def test_invalid_section(self): def test_invalid_section(self):
"""Test an invalid section.""" """Test an invalid section."""
self.cp.read_dict({'foo': {'bar': 'baz'}}) self.cp.read_dict({'foo': {'bar': 'baz'}})
with self.assertRaises(configexc.NoSectionError): with pytest.raises(configexc.NoSectionError):
self.cfg._from_cp(self.cp) self.cfg._from_cp(self.cp)
def test_invalid_option(self): def test_invalid_option(self):
"""Test an invalid option.""" """Test an invalid option."""
self.cp.read_dict({'general': {'bar': 'baz'}}) self.cp.read_dict({'general': {'bar': 'baz'}})
with self.assertRaises(configexc.NoOptionError): with pytest.raises(configexc.NoOptionError):
self.cfg._from_cp(self.cp) self.cfg._from_cp(self.cp)
def test_invalid_section_relaxed(self): def test_invalid_section_relaxed(self):
"""Test an invalid section with relaxed=True.""" """Test an invalid section with relaxed=True."""
self.cp.read_dict({'foo': {'bar': 'baz'}}) self.cp.read_dict({'foo': {'bar': 'baz'}})
self.cfg._from_cp(self.cp, relaxed=True) self.cfg._from_cp(self.cp, relaxed=True)
with self.assertRaises(configexc.NoSectionError): with pytest.raises(configexc.NoSectionError):
self.cfg.get('foo', 'bar') # pylint: disable=bad-config-call self.cfg.get('foo', 'bar') # pylint: disable=bad-config-call
def test_invalid_option_relaxed(self): def test_invalid_option_relaxed(self):
"""Test an invalid option with relaxed=True.""" """Test an invalid option with relaxed=True."""
self.cp.read_dict({'general': {'bar': 'baz'}}) self.cp.read_dict({'general': {'bar': 'baz'}})
self.cfg._from_cp(self.cp, relaxed=True) self.cfg._from_cp(self.cp, relaxed=True)
with self.assertRaises(configexc.NoOptionError): with pytest.raises(configexc.NoOptionError):
self.cfg.get('general', 'bar') # pylint: disable=bad-config-call self.cfg.get('general', 'bar') # pylint: disable=bad-config-call
class DefaultConfigTests(unittest.TestCase): class TestDefaultConfig:
"""Test validating of the default config.""" """Test validating of the default config."""
@ -169,40 +165,32 @@ class DefaultConfigTests(unittest.TestCase):
conf._validate_all() conf._validate_all()
class ConfigInitTests(unittest.TestCase): class TestConfigInit:
"""Test initializing of the config.""" """Test initializing of the config."""
def setUp(self): @pytest.yield_fixture(autouse=True)
self.temp_dir = tempfile.mkdtemp() def setup(self, tmpdir):
self.conf_path = os.path.join(self.temp_dir, 'config') self.conf_path = (tmpdir / 'config').ensure(dir=1)
self.data_path = os.path.join(self.temp_dir, 'data') self.data_path = (tmpdir / 'data').ensure(dir=1)
self.cache_path = os.path.join(self.temp_dir, 'cache') self.cache_path = (tmpdir / 'cache').ensure(dir=1)
os.mkdir(self.conf_path)
os.mkdir(self.data_path)
os.mkdir(self.cache_path)
self.env = { self.env = {
'XDG_CONFIG_HOME': self.conf_path, 'XDG_CONFIG_HOME': str(self.conf_path),
'XDG_DATA_HOME': self.data_path, 'XDG_DATA_HOME': str(self.data_path),
'XDG_CACHE_HOME': self.cache_path, 'XDG_CACHE_HOME': str(self.cache_path),
} }
objreg.register('app', QObject()) objreg.register('app', QObject())
objreg.register('save-manager', mock.MagicMock()) objreg.register('save-manager', mock.MagicMock())
args = argparse.Namespace(relaxed_config=False) args = argparse.Namespace(relaxed_config=False)
objreg.register('args', args) objreg.register('args', args)
yield
def tearDown(self):
shutil.rmtree(self.temp_dir)
objreg.global_registry.clear() objreg.global_registry.clear()
def test_config_none(self): def test_config_none(self, monkeypatch):
"""Test initializing with config path set to None.""" """Test initializing with config path set to None."""
args = types.SimpleNamespace(confdir='') args = types.SimpleNamespace(confdir='')
with helpers.environ_set_temp(self.env): for k, v in self.env.items():
standarddir.init(args) monkeypatch.setenv(k, v)
config.init() standarddir.init(args)
self.assertFalse(os.listdir(self.conf_path)) config.init()
assert not os.listdir(str(self.conf_path))
if __name__ == '__main__':
unittest.main()

78
tests/conftest.py Normal file
View File

@ -0,0 +1,78 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# 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/>.
"""The qutebrowser test suite contest file."""
import pytest
@pytest.fixture(scope='session', autouse=True)
def app_and_logging(qapp):
"""Initialize a QApplication and logging.
This ensures that a QApplication is created and used by all tests.
"""
from log import init
init()
@pytest.fixture(scope='session')
def stubs():
"""Provide access to stub objects useful for testing."""
import stubs
return stubs
@pytest.fixture(scope='session')
def unicode_encode_err():
"""Provide a fake UnicodeEncodeError exception."""
return UnicodeEncodeError('ascii', # codec
'', # object
0, # start
2, # end
'fake exception') # reason
@pytest.fixture
def webpage():
"""Get a new QWebPage object."""
from PyQt5.QtWebKitWidgets import QWebPage
from PyQt5.QtNetwork import QNetworkAccessManager
page = QWebPage()
nam = page.networkAccessManager()
nam.setNetworkAccessible(QNetworkAccessManager.NotAccessible)
return page
@pytest.fixture
def fake_keyevent_factory():
"""Fixture that when called will return a mock instance of a QKeyEvent."""
from unittest import mock
from PyQt5.QtGui import QKeyEvent
def fake_keyevent(key, modifiers=0, text=''):
"""Generate a new fake QKeyPressEvent."""
evtmock = mock.create_autospec(QKeyEvent, instance=True)
evtmock.key.return_value = key
evtmock.modifiers.return_value = modifiers
evtmock.text.return_value = text
return evtmock
return fake_keyevent

View File

@ -0,0 +1,289 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>:
#
# 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/>.
# pylint: disable=protected-access
"""Tests for BaseKeyParser."""
import logging
from unittest import mock
from PyQt5.QtCore import Qt
import pytest
from qutebrowser.keyinput import basekeyparser
from qutebrowser.utils import objreg, log
CONFIG = {'input': {'timeout': 100}}
BINDINGS = {'test': {'<Ctrl-a>': 'ctrla',
'a': 'a',
'ba': 'ba',
'ax': 'ax',
'ccc': 'ccc'},
'test2': {'foo': 'bar', '<Ctrl+X>': 'ctrlx'}}
@pytest.yield_fixture
def fake_keyconfig():
"""Create a mock of a KeyConfiguration and register it into objreg."""
fake_keyconfig = mock.Mock(spec=['get_bindings_for'])
fake_keyconfig.get_bindings_for.side_effect = lambda s: BINDINGS[s]
objreg.register('key-config', fake_keyconfig)
yield
objreg.delete('key-config')
@pytest.fixture
def mock_timer(mocker, stubs):
"""Mock the Timer class used by the usertypes module with a stub."""
mocker.patch('qutebrowser.keyinput.basekeyparser.usertypes.Timer',
new=stubs.FakeTimer)
class TestSplitCount:
"""Test the _split_count method.
Attributes:
kp: The BaseKeyParser we're testing.
"""
@pytest.fixture(autouse=True)
def setup(self):
self.kp = basekeyparser.BaseKeyParser(0, supports_count=True)
def test_onlycount(self):
"""Test split_count with only a count."""
self.kp._keystring = '10'
assert self.kp._split_count() == (10, '')
def test_normalcount(self):
"""Test split_count with count and text."""
self.kp._keystring = '10foo'
assert self.kp._split_count() == (10, 'foo')
def test_minuscount(self):
"""Test split_count with a negative count."""
self.kp._keystring = '-1foo'
assert self.kp._split_count() == (None, '-1foo')
def test_expcount(self):
"""Test split_count with an exponential count."""
self.kp._keystring = '10e4foo'
assert self.kp._split_count() == (10, 'e4foo')
def test_nocount(self):
"""Test split_count with only a command."""
self.kp._keystring = 'foo'
assert self.kp._split_count() == (None, 'foo')
def test_nosupport(self):
"""Test split_count with a count when counts aren't supported."""
self.kp._supports_count = False
self.kp._keystring = '10foo'
assert self.kp._split_count() == (None, '10foo')
@pytest.mark.usefixtures('fake_keyconfig', 'mock_timer')
class TestReadConfig:
"""Test reading the config."""
def test_read_config_invalid(self):
"""Test reading config without setting it before."""
kp = basekeyparser.BaseKeyParser(0)
with pytest.raises(ValueError):
kp.read_config()
def test_read_config_valid(self):
"""Test reading config."""
kp = basekeyparser.BaseKeyParser(0, supports_count=True,
supports_chains=True)
kp.read_config('test')
assert 'ccc' in kp.bindings
assert 'ctrl+a' in kp.special_bindings
kp.read_config('test2')
assert 'ccc' not in kp.bindings
assert 'ctrl+a' not in kp.special_bindings
assert 'foo' in kp.bindings
assert 'ctrl+x' in kp.special_bindings
@pytest.mark.usefixtures('mock_timer')
class TestSpecialKeys:
"""Check execute() with special keys.
Attributes:
kp: The BaseKeyParser to be tested.
"""
@pytest.fixture(autouse=True)
def setup(self, caplog, fake_keyconfig):
self.kp = basekeyparser.BaseKeyParser(0)
self.kp.execute = mock.Mock()
with caplog.atLevel(logging.WARNING, log.keyboard.name):
# Ignoring keychain 'ccc' in mode 'test' because keychains are not
# supported there.
self.kp.read_config('test')
def test_valid_key(self, fake_keyevent_factory):
"""Test a valid special keyevent."""
self.kp.handle(fake_keyevent_factory(Qt.Key_A, Qt.ControlModifier))
self.kp.handle(fake_keyevent_factory(Qt.Key_X, Qt.ControlModifier))
self.kp.execute.assert_called_once_with('ctrla', self.kp.Type.special)
def test_invalid_key(self, fake_keyevent_factory):
"""Test an invalid special keyevent."""
self.kp.handle(fake_keyevent_factory(Qt.Key_A, (Qt.ControlModifier |
Qt.AltModifier)))
assert not self.kp.execute.called
def test_keychain(self, fake_keyevent_factory):
"""Test a keychain."""
self.kp.handle(fake_keyevent_factory(Qt.Key_B))
self.kp.handle(fake_keyevent_factory(Qt.Key_A))
assert not self.kp.execute.called
@pytest.mark.usefixtures('mock_timer')
class TestKeyChain:
"""Test execute() with keychain support.
Attributes:
kp: The BaseKeyParser to be tested.
"""
@pytest.fixture(autouse=True)
def setup(self, fake_keyconfig):
"""Set up mocks and read the test config."""
self.kp = basekeyparser.BaseKeyParser(0, supports_chains=True,
supports_count=False)
self.kp.execute = mock.Mock()
self.kp.read_config('test')
def test_valid_special_key(self, fake_keyevent_factory):
"""Test valid special key."""
self.kp.handle(fake_keyevent_factory(Qt.Key_A, Qt.ControlModifier))
self.kp.handle(fake_keyevent_factory(Qt.Key_X, Qt.ControlModifier))
self.kp.execute.assert_called_once_with('ctrla', self.kp.Type.special)
assert self.kp._keystring == ''
def test_invalid_special_key(self, fake_keyevent_factory):
"""Test invalid special key."""
self.kp.handle(fake_keyevent_factory(Qt.Key_A, (Qt.ControlModifier |
Qt.AltModifier)))
assert not self.kp.execute.called
assert self.kp._keystring == ''
def test_keychain(self, fake_keyevent_factory):
"""Test valid keychain."""
# Press 'x' which is ignored because of no match
self.kp.handle(fake_keyevent_factory(Qt.Key_X, text='x'))
# Then start the real chain
self.kp.handle(fake_keyevent_factory(Qt.Key_B, text='b'))
self.kp.handle(fake_keyevent_factory(Qt.Key_A, text='a'))
self.kp.execute.assert_called_once_with('ba', self.kp.Type.chain, None)
assert self.kp._keystring == ''
def test_ambiguous_keychain(self, fake_keyevent_factory, mocker, stubs):
"""Test ambiguous keychain."""
mocker.patch('qutebrowser.keyinput.basekeyparser.config',
new=stubs.ConfigStub(CONFIG))
timer = self.kp._ambiguous_timer
assert not timer.isActive()
# We start with 'a' where the keychain gives us an ambiguous result.
# Then we check if the timer has been set up correctly
self.kp.handle(fake_keyevent_factory(Qt.Key_A, text='a'))
assert not self.kp.execute.called
assert timer.isSingleShot()
assert timer.interval() == 100
assert timer.isActive()
# Now we type an 'x' and check 'ax' has been executed and the timer
# stopped.
self.kp.handle(fake_keyevent_factory(Qt.Key_X, text='x'))
self.kp.execute.assert_called_once_with('ax', self.kp.Type.chain, None)
assert not timer.isActive()
assert self.kp._keystring == ''
def test_invalid_keychain(self, fake_keyevent_factory):
"""Test invalid keychain."""
self.kp.handle(fake_keyevent_factory(Qt.Key_B, text='b'))
self.kp.handle(fake_keyevent_factory(Qt.Key_C, text='c'))
assert self.kp._keystring == ''
@pytest.mark.usefixtures('mock_timer')
class TestCount:
"""Test execute() with counts."""
@pytest.fixture(autouse=True)
def setup(self, fake_keyconfig):
self.kp = basekeyparser.BaseKeyParser(0, supports_chains=True,
supports_count=True)
self.kp.execute = mock.Mock()
self.kp.read_config('test')
def test_no_count(self, fake_keyevent_factory):
"""Test with no count added."""
self.kp.handle(fake_keyevent_factory(Qt.Key_B, text='b'))
self.kp.handle(fake_keyevent_factory(Qt.Key_A, text='a'))
self.kp.execute.assert_called_once_with('ba', self.kp.Type.chain, None)
assert self.kp._keystring == ''
def test_count_0(self, fake_keyevent_factory):
"""Test with count=0."""
self.kp.handle(fake_keyevent_factory(Qt.Key_0, text='0'))
self.kp.handle(fake_keyevent_factory(Qt.Key_B, text='b'))
self.kp.handle(fake_keyevent_factory(Qt.Key_A, text='a'))
self.kp.execute.assert_called_once_with('ba', self.kp.Type.chain, 0)
assert self.kp._keystring == ''
def test_count_42(self, fake_keyevent_factory):
"""Test with count=42."""
self.kp.handle(fake_keyevent_factory(Qt.Key_4, text='4'))
self.kp.handle(fake_keyevent_factory(Qt.Key_2, text='2'))
self.kp.handle(fake_keyevent_factory(Qt.Key_B, text='b'))
self.kp.handle(fake_keyevent_factory(Qt.Key_A, text='a'))
self.kp.execute.assert_called_once_with('ba', self.kp.Type.chain, 42)
assert self.kp._keystring == ''
def test_count_42_invalid(self, fake_keyevent_factory):
"""Test with count=42 and invalid command."""
# Invalid call with ccx gets ignored
self.kp.handle(fake_keyevent_factory(Qt.Key_4, text='4'))
self.kp.handle(fake_keyevent_factory(Qt.Key_2, text='2'))
self.kp.handle(fake_keyevent_factory(Qt.Key_B, text='c'))
self.kp.handle(fake_keyevent_factory(Qt.Key_A, text='c'))
self.kp.handle(fake_keyevent_factory(Qt.Key_A, text='x'))
assert not self.kp.execute.called
assert self.kp._keystring == ''
# Valid call with ccc gets the correct count
self.kp.handle(fake_keyevent_factory(Qt.Key_4, text='2'))
self.kp.handle(fake_keyevent_factory(Qt.Key_2, text='3'))
self.kp.handle(fake_keyevent_factory(Qt.Key_B, text='c'))
self.kp.handle(fake_keyevent_factory(Qt.Key_A, text='c'))
self.kp.handle(fake_keyevent_factory(Qt.Key_A, text='c'))
self.kp.execute.assert_called_once_with('ccc', self.kp.Type.chain, 23)
assert self.kp._keystring == ''

View File

@ -21,11 +21,10 @@
from PyQt5.QtCore import Qt from PyQt5.QtCore import Qt
import unittest
from unittest import mock from unittest import mock
import pytest
from qutebrowser.keyinput import modeparsers from qutebrowser.keyinput import modeparsers
from qutebrowser.test import stubs, helpers
from qutebrowser.utils import objreg from qutebrowser.utils import objreg
@ -39,11 +38,7 @@ fake_keyconfig = mock.Mock(spec=['get_bindings_for'])
fake_keyconfig.get_bindings_for.side_effect = lambda s: BINDINGS[s] fake_keyconfig.get_bindings_for.side_effect = lambda s: BINDINGS[s]
@mock.patch('qutebrowser.keyinput.basekeyparser.usertypes.Timer', class TestsNormalKeyParser:
new=stubs.FakeTimer)
@mock.patch('qutebrowser.keyinput.modeparsers.config',
new=stubs.ConfigStub(CONFIG))
class NormalKeyParserTests(unittest.TestCase):
"""Tests for NormalKeyParser. """Tests for NormalKeyParser.
@ -53,46 +48,47 @@ class NormalKeyParserTests(unittest.TestCase):
# pylint: disable=protected-access # pylint: disable=protected-access
def setUp(self): @pytest.yield_fixture(autouse=True)
def setup(self, mocker, stubs):
"""Set up mocks and read the test config.""" """Set up mocks and read the test config."""
mocker.patch('qutebrowser.keyinput.basekeyparser.usertypes.Timer',
new=stubs.FakeTimer)
mocker.patch('qutebrowser.keyinput.modeparsers.config',
new=stubs.ConfigStub(CONFIG))
objreg.register('key-config', fake_keyconfig) objreg.register('key-config', fake_keyconfig)
self.kp = modeparsers.NormalKeyParser(0) self.kp = modeparsers.NormalKeyParser(0)
self.kp.execute = mock.Mock() self.kp.execute = mock.Mock()
yield
def tearDown(self):
objreg.delete('key-config') objreg.delete('key-config')
def test_keychain(self): def test_keychain(self, fake_keyevent_factory):
"""Test valid keychain.""" """Test valid keychain."""
# Press 'x' which is ignored because of no match # Press 'x' which is ignored because of no match
self.kp.handle(helpers.fake_keyevent(Qt.Key_X, text='x')) self.kp.handle(fake_keyevent_factory(Qt.Key_X, text='x'))
# Then start the real chain # Then start the real chain
self.kp.handle(helpers.fake_keyevent(Qt.Key_B, text='b')) self.kp.handle(fake_keyevent_factory(Qt.Key_B, text='b'))
self.kp.handle(helpers.fake_keyevent(Qt.Key_A, text='a')) self.kp.handle(fake_keyevent_factory(Qt.Key_A, text='a'))
self.kp.execute.assert_called_once_with('ba', self.kp.Type.chain, None) self.kp.execute.assert_called_once_with('ba', self.kp.Type.chain, None)
self.assertEqual(self.kp._keystring, '') assert self.kp._keystring == ''
def test_partial_keychain_timeout(self): def test_partial_keychain_timeout(self, fake_keyevent_factory):
"""Test partial keychain timeout.""" """Test partial keychain timeout."""
timer = self.kp._partial_timer timer = self.kp._partial_timer
self.assertFalse(timer.isActive()) assert not timer.isActive()
# Press 'b' for a partial match. # Press 'b' for a partial match.
# Then we check if the timer has been set up correctly # Then we check if the timer has been set up correctly
self.kp.handle(helpers.fake_keyevent(Qt.Key_B, text='b')) self.kp.handle(fake_keyevent_factory(Qt.Key_B, text='b'))
self.assertTrue(timer.isSingleShot()) assert timer.isSingleShot()
self.assertEqual(timer.interval(), 100) assert timer.interval() == 100
self.assertTrue(timer.isActive()) assert timer.isActive()
self.assertFalse(self.kp.execute.called) assert not self.kp.execute.called
self.assertEqual(self.kp._keystring, 'b') assert self.kp._keystring == 'b'
# Now simulate a timeout and check the keystring has been cleared. # Now simulate a timeout and check the keystring has been cleared.
keystring_updated_mock = mock.Mock() keystring_updated_mock = mock.Mock()
self.kp.keystring_updated.connect(keystring_updated_mock) self.kp.keystring_updated.connect(keystring_updated_mock)
timer.timeout.emit() timer.timeout.emit()
self.assertFalse(self.kp.execute.called) assert not self.kp.execute.called
self.assertEqual(self.kp._keystring, '') assert self.kp._keystring == ''
keystring_updated_mock.assert_called_once_with('') keystring_updated_mock.assert_called_once_with('')
if __name__ == '__main__':
unittest.main()

View File

@ -0,0 +1,52 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# 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/>.
"""pytest fixtures and utilities for testing.
Fixtures defined here will be visible to all test files in this directory and
below.
"""
import pytest
from qutebrowser.config.config import ConfigManager
from qutebrowser.utils import objreg
@pytest.yield_fixture
def default_config():
"""
Fixture that registers an empty config object into the objreg module.
Should be used by tests which create widgets that obtain their initial
state from the global config object.
Note:
If we declare this fixture like this:
@pytest.yield_fixture(autouse=True)
Then all tests below this file will have a default config registered
and ready for use. Is that desirable?
"""
config_obj = ConfigManager(configdir=None, fname=None, relaxed=True)
objreg.register('config', config_obj)
yield config_obj
objreg.delete('config')

View File

@ -17,19 +17,32 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>. # along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
"""The qutebrowser test suite."""
import atexit """Test Percentage widget."""
from PyQt5.QtWidgets import QApplication import pytest
from qutebrowser.test import log from qutebrowser.mainwindow.statusbar.percentage import Percentage
# We create a singleton QApplication here.
qApp = QApplication([]) @pytest.mark.parametrize('y, expected', [
qApp.setApplicationName('qutebrowser') (0, '[top]'),
qApp.processEvents() (100, '[bot]'),
atexit.register(qApp.processEvents) (75, '[75%]'),
atexit.register(qApp.quit) (25, '[25%]'),
log.init() (5, '[ 5%]'),
])
def test_percentage_text(qtbot, y, expected):
"""
Test text displayed by the widget based on the y position of a page.
Args:
qtbot: pytestqt.plugin.QtBot fixture
y: y position of the page as an int in the range [0, 100].
parametrized.
expected: expected text given y position. parametrized.
"""
percentage = Percentage()
qtbot.add_widget(percentage)
percentage.set_perc(None, y=y)
assert percentage.text() == expected

View File

@ -0,0 +1,74 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# 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/>.
"""Test Progress widget."""
from collections import namedtuple
import pytest
from qutebrowser.browser import webview
from qutebrowser.mainwindow.statusbar.progress import Progress
@pytest.fixture
def progress_widget(qtbot, default_config):
"""Create a Progress widget and checks its initial state."""
widget = Progress()
qtbot.add_widget(widget)
assert not widget.isVisible()
assert not widget.isTextVisible()
return widget
def test_load_started(progress_widget):
"""Ensure the Progress widget reacts properly when the page starts loading.
Args:
progress_widget: Progress widget that will be tested.
"""
progress_widget.on_load_started()
assert progress_widget.value() == 0
assert progress_widget.isVisible()
# mock tab object
Tab = namedtuple('Tab', 'progress load_status')
@pytest.mark.parametrize('tab, expected_visible', [
(Tab(15, webview.LoadStatus.loading), True),
(Tab(100, webview.LoadStatus.success), False),
(Tab(100, webview.LoadStatus.error), False),
(Tab(100, webview.LoadStatus.warn), False),
(Tab(100, webview.LoadStatus.none), False),
])
def test_tab_changed(progress_widget, tab, expected_visible):
"""Test that progress widget value and visibility state match expectations.
This uses a dummy Tab object.
Args:
progress_widget: Progress widget that will be tested.
"""
progress_widget.on_tab_changed(tab)
actual = progress_widget.value(), progress_widget.isVisible()
expected = tab.progress, expected_visible
assert actual == expected

View File

@ -0,0 +1,53 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# 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/>.
"""Test TextBase widget."""
from PyQt5.QtCore import Qt
import pytest
from qutebrowser.mainwindow.statusbar.textbase import TextBase
@pytest.mark.parametrize('elidemode, check', [
(Qt.ElideRight, lambda s: s.endswith('')),
(Qt.ElideLeft, lambda s: s.startswith('')),
(Qt.ElideMiddle, lambda s: '' in s),
(Qt.ElideNone, lambda s: '' not in s),
])
def test_elided_text(qtbot, elidemode, check):
"""Ensure that a widget too small to hold the entire label text will elide.
It is difficult to check what is actually being drawn in a portable way, so
at least we ensure our customized methods are being called and the elided
string contains the horizontal ellipsis character.
Args:
qtbot: pytestqt.plugin.QtBot fixture
elidemode: parametrized elide mode
check: function that receives the elided text and must return True
if the elipsis is placed correctly according to elidemode.
"""
label = TextBase(elidemode=elidemode)
qtbot.add_widget(label)
long_string = 'Hello world! ' * 20
label.setText(long_string)
label.resize(100, 50)
label.show()
assert check(label._elided_text) # pylint: disable=protected-access

View File

@ -23,19 +23,16 @@
import os import os
import os.path import os.path
import unittest
import logging import logging
from unittest import mock from unittest import mock
from PyQt5.QtCore import QProcess from PyQt5.QtCore import QProcess
import pytest
from qutebrowser.misc import editor from qutebrowser.misc import editor
from qutebrowser.test import stubs, helpers
@mock.patch('qutebrowser.misc.editor.QProcess', class TestArg:
new_callable=stubs.FakeQProcess)
class ArgTests(unittest.TestCase):
"""Test argument handling. """Test argument handling.
@ -43,52 +40,48 @@ class ArgTests(unittest.TestCase):
editor: The ExternalEditor instance to test. editor: The ExternalEditor instance to test.
""" """
def setUp(self): @pytest.yield_fixture(autouse=True)
def setup(self, mocker, stubs):
mocker.patch('qutebrowser.misc.editor.QProcess',
new_callable=stubs.FakeQProcess)
self.config = stubs.ConfigStub()
mocker.patch('qutebrowser.misc.editor.config', new=self.config)
self.editor = editor.ExternalEditor(0) self.editor = editor.ExternalEditor(0)
yield
self.editor._cleanup() # pylint: disable=protected-access
@mock.patch('qutebrowser.misc.editor.config', new=stubs.ConfigStub( def test_simple_start_args(self):
{'general': {'editor': ['bin'], 'editor-encoding': 'utf-8'}}))
def test_simple_start_args(self, _proc_mock):
"""Test starting editor without arguments.""" """Test starting editor without arguments."""
self.config.data = {
'general': {'editor': ['bin'], 'editor-encoding': 'utf-8'}}
self.editor.edit("") self.editor.edit("")
self.editor._proc.start.assert_called_with("bin", []) self.editor._proc.start.assert_called_with("bin", [])
@mock.patch('qutebrowser.misc.editor.config', new=stubs.ConfigStub( def test_start_args(self):
{'general': {'editor': ['bin', 'foo', 'bar'],
'editor-encoding': 'utf-8'}}))
def test_start_args(self, _proc_mock):
"""Test starting editor with static arguments.""" """Test starting editor with static arguments."""
self.config.data = {'general': {'editor': ['bin', 'foo', 'bar'],
'editor-encoding': 'utf-8'}}
self.editor.edit("") self.editor.edit("")
self.editor._proc.start.assert_called_with("bin", ["foo", "bar"]) self.editor._proc.start.assert_called_with("bin", ["foo", "bar"])
@mock.patch('qutebrowser.misc.editor.config', new=stubs.ConfigStub( def test_placeholder(self):
{'general': {'editor': ['bin', 'foo', '{}', 'bar'],
'editor-encoding': 'utf-8'}}))
def test_placeholder(self, _proc_mock):
"""Test starting editor with placeholder argument.""" """Test starting editor with placeholder argument."""
self.config.data = {'general': {'editor': ['bin', 'foo', '{}', 'bar'],
'editor-encoding': 'utf-8'}}
self.editor.edit("") self.editor.edit("")
filename = self.editor._filename filename = self.editor._filename
self.editor._proc.start.assert_called_with( self.editor._proc.start.assert_called_with(
"bin", ["foo", filename, "bar"]) "bin", ["foo", filename, "bar"])
@mock.patch('qutebrowser.misc.editor.config', new=stubs.ConfigStub( def test_in_arg_placeholder(self):
{'general': {'editor': ['bin', 'foo{}bar'],
'editor-encoding': 'utf-8'}}))
def test_in_arg_placeholder(self, _proc_mock):
"""Test starting editor with placeholder argument inside argument.""" """Test starting editor with placeholder argument inside argument."""
self.config.data = {'general': {'editor': ['bin', 'foo{}bar'],
'editor-encoding': 'utf-8'}}
self.editor.edit("") self.editor.edit("")
self.editor._proc.start.assert_called_with("bin", ["foo{}bar"]) self.editor._proc.start.assert_called_with("bin", ["foo{}bar"])
def tearDown(self):
self.editor._cleanup() # pylint: disable=protected-access
class TestFileHandling:
@mock.patch('qutebrowser.misc.editor.message', new=helpers.MessageModule())
@mock.patch('qutebrowser.misc.editor.QProcess',
new_callable=stubs.FakeQProcess)
@mock.patch('qutebrowser.misc.editor.config', new=stubs.ConfigStub(
{'general': {'editor': [''], 'editor-encoding': 'utf-8'}}))
class FileHandlingTests(unittest.TestCase):
"""Test creation/deletion of tempfile. """Test creation/deletion of tempfile.
@ -96,42 +89,49 @@ class FileHandlingTests(unittest.TestCase):
editor: The ExternalEditor instance to test. editor: The ExternalEditor instance to test.
""" """
def setUp(self): @pytest.fixture(autouse=True)
def setup(self, mocker, stubs):
mocker.patch('qutebrowser.misc.editor.message',
new=stubs.MessageModule())
mocker.patch('qutebrowser.misc.editor.QProcess',
new_callable=stubs.FakeQProcess)
mocker.patch('qutebrowser.misc.editor.config',
new=stubs.ConfigStub(
{'general': {'editor': [''],
'editor-encoding': 'utf-8'}}))
self.editor = editor.ExternalEditor(0) self.editor = editor.ExternalEditor(0)
def test_file_handling_closed_ok(self, _proc_mock): def test_file_handling_closed_ok(self):
"""Test file handling when closing with an exit status == 0.""" """Test file handling when closing with an exit status == 0."""
self.editor.edit("") self.editor.edit("")
filename = self.editor._filename filename = self.editor._filename
self.assertTrue(os.path.exists(filename)) assert os.path.exists(filename)
self.editor.on_proc_closed(0, QProcess.NormalExit) self.editor.on_proc_closed(0, QProcess.NormalExit)
self.assertFalse(os.path.exists(filename)) assert not os.path.exists(filename)
def test_file_handling_closed_error(self, _proc_mock): def test_file_handling_closed_error(self, caplog):
"""Test file handling when closing with an exit status != 0.""" """Test file handling when closing with an exit status != 0."""
self.editor.edit("") self.editor.edit("")
filename = self.editor._filename filename = self.editor._filename
self.assertTrue(os.path.exists(filename)) assert os.path.exists(filename)
with self.assertLogs('message', logging.ERROR): with caplog.atLevel(logging.ERROR):
self.editor.on_proc_closed(1, QProcess.NormalExit) self.editor.on_proc_closed(1, QProcess.NormalExit)
self.assertFalse(os.path.exists(filename)) assert len(caplog.records()) == 2
assert not os.path.exists(filename)
def test_file_handling_closed_crash(self, _proc_mock): def test_file_handling_closed_crash(self, caplog):
"""Test file handling when closing with a crash.""" """Test file handling when closing with a crash."""
self.editor.edit("") self.editor.edit("")
filename = self.editor._filename filename = self.editor._filename
self.assertTrue(os.path.exists(filename)) assert os.path.exists(filename)
with self.assertLogs('message', logging.ERROR): with caplog.atLevel(logging.ERROR):
self.editor.on_proc_error(QProcess.Crashed) self.editor.on_proc_error(QProcess.Crashed)
assert len(caplog.records()) == 2
self.editor.on_proc_closed(0, QProcess.CrashExit) self.editor.on_proc_closed(0, QProcess.CrashExit)
self.assertFalse(os.path.exists(filename)) assert not os.path.exists(filename)
@mock.patch('qutebrowser.misc.editor.QProcess', class TestModifyTests:
new_callable=stubs.FakeQProcess)
@mock.patch('qutebrowser.misc.editor.config', new=stubs.ConfigStub(
{'general': {'editor': [''], 'editor-encoding': 'utf-8'}}))
class TextModifyTests(unittest.TestCase):
"""Tests to test if the text gets saved/loaded correctly. """Tests to test if the text gets saved/loaded correctly.
@ -139,7 +139,12 @@ class TextModifyTests(unittest.TestCase):
editor: The ExternalEditor instance to test. editor: The ExternalEditor instance to test.
""" """
def setUp(self): @pytest.fixture(autouse=True)
def setup(self, mocker, stubs):
mocker.patch('qutebrowser.misc.editor.QProcess',
new_callable=stubs.FakeQProcess)
mocker.patch('qutebrowser.misc.editor.config', new=stubs.ConfigStub(
{'general': {'editor': [''], 'editor-encoding': 'utf-8'}}))
self.editor = editor.ExternalEditor(0) self.editor = editor.ExternalEditor(0)
self.editor.editing_finished = mock.Mock() self.editor.editing_finished = mock.Mock()
@ -164,45 +169,40 @@ class TextModifyTests(unittest.TestCase):
data = f.read() data = f.read()
return data return data
def test_empty_input(self, _proc_mock): def test_empty_input(self):
"""Test if an empty input gets modified correctly.""" """Test if an empty input gets modified correctly."""
self.editor.edit("") self.editor.edit("")
self.assertEqual(self._read(), "") assert self._read() == ""
self._write("Hello") self._write("Hello")
self.editor.on_proc_closed(0, QProcess.NormalExit) self.editor.on_proc_closed(0, QProcess.NormalExit)
self.editor.editing_finished.emit.assert_called_with("Hello") self.editor.editing_finished.emit.assert_called_with("Hello")
def test_simple_input(self, _proc_mock): def test_simple_input(self):
"""Test if an empty input gets modified correctly.""" """Test if an empty input gets modified correctly."""
self.editor.edit("Hello") self.editor.edit("Hello")
self.assertEqual(self._read(), "Hello") assert self._read() == "Hello"
self._write("World") self._write("World")
self.editor.on_proc_closed(0, QProcess.NormalExit) self.editor.on_proc_closed(0, QProcess.NormalExit)
self.editor.editing_finished.emit.assert_called_with("World") self.editor.editing_finished.emit.assert_called_with("World")
def test_umlaut(self, _proc_mock): def test_umlaut(self):
"""Test if umlauts works correctly.""" """Test if umlauts works correctly."""
self.editor.edit("Hällö Wörld") self.editor.edit("Hällö Wörld")
self.assertEqual(self._read(), "Hällö Wörld") assert self._read() == "Hällö Wörld"
self._write("Überprüfung") self._write("Überprüfung")
self.editor.on_proc_closed(0, QProcess.NormalExit) self.editor.on_proc_closed(0, QProcess.NormalExit)
self.editor.editing_finished.emit.assert_called_with("Überprüfung") self.editor.editing_finished.emit.assert_called_with("Überprüfung")
def test_unicode(self, _proc_mock): def test_unicode(self):
"""Test if other UTF8 chars work correctly.""" """Test if other UTF8 chars work correctly."""
self.editor.edit("\u2603") # Unicode snowman self.editor.edit("\u2603") # Unicode snowman
self.assertEqual(self._read(), "\u2603") assert self._read() == "\u2603"
self._write("\u2601") # Cloud self._write("\u2601") # Cloud
self.editor.on_proc_closed(0, QProcess.NormalExit) self.editor.on_proc_closed(0, QProcess.NormalExit)
self.editor.editing_finished.emit.assert_called_with("\u2601") self.editor.editing_finished.emit.assert_called_with("\u2601")
@mock.patch('qutebrowser.misc.editor.QProcess', class TestErrorMessage:
new_callable=stubs.FakeQProcess)
@mock.patch('qutebrowser.misc.editor.message', new=helpers.MessageModule())
@mock.patch('qutebrowser.misc.editor.config', new=stubs.ConfigStub(
{'general': {'editor': [''], 'editor-encoding': 'utf-8'}}))
class ErrorMessageTests(unittest.TestCase):
"""Test if statusbar error messages get emitted correctly. """Test if statusbar error messages get emitted correctly.
@ -210,21 +210,28 @@ class ErrorMessageTests(unittest.TestCase):
editor: The ExternalEditor instance to test. editor: The ExternalEditor instance to test.
""" """
def setUp(self): @pytest.yield_fixture(autouse=True)
def setup(self, mocker, stubs):
mocker.patch('qutebrowser.misc.editor.QProcess',
new_callable=stubs.FakeQProcess)
mocker.patch('qutebrowser.misc.editor.message',
new=stubs.MessageModule())
mocker.patch('qutebrowser.misc.editor.config', new=stubs.ConfigStub(
{'general': {'editor': [''], 'editor-encoding': 'utf-8'}}))
self.editor = editor.ExternalEditor(0) self.editor = editor.ExternalEditor(0)
yield
self.editor._cleanup() # pylint: disable=protected-access
def test_proc_error(self, _proc_mock): def test_proc_error(self, caplog):
"""Test on_proc_error.""" """Test on_proc_error."""
self.editor.edit("") self.editor.edit("")
with self.assertLogs('message', logging.ERROR): with caplog.atLevel(logging.ERROR, 'message'):
self.editor.on_proc_error(QProcess.Crashed) self.editor.on_proc_error(QProcess.Crashed)
assert len(caplog.records()) == 2
def test_proc_return(self, _proc_mock): def test_proc_return(self, caplog):
"""Test on_proc_finished with a bad exit status.""" """Test on_proc_finished with a bad exit status."""
self.editor.edit("") self.editor.edit("")
with self.assertLogs('message', logging.ERROR): with caplog.atLevel(logging.ERROR, 'message'):
self.editor.on_proc_closed(1, QProcess.NormalExit) self.editor.on_proc_closed(1, QProcess.NormalExit)
assert len(caplog.records()) == 3
if __name__ == '__main__':
unittest.main()

View File

@ -22,40 +22,40 @@
# pylint: disable=protected-access # pylint: disable=protected-access
import inspect import inspect
import unittest
from unittest import mock from unittest import mock
from PyQt5.QtWidgets import QLineEdit from PyQt5.QtWidgets import QLineEdit
import pytest
from qutebrowser.misc import readline from qutebrowser.misc import readline
from qutebrowser.test import stubs
@mock.patch('qutebrowser.misc.readline.QApplication', @pytest.fixture
new_callable=stubs.FakeQApplication) def mocked_qapp(mocker, stubs):
class NoneWidgetTests(unittest.TestCase): """Fixture that mocks readline.QApplication and returns it."""
return mocker.patch('qutebrowser.misc.readline.QApplication',
new_callable=stubs.FakeQApplication)
"""Tests when the focused widget is None."""
def setUp(self): class TestNoneWidget:
"""Test if there are no exceptions when the widget is None."""
def test_none(self, mocked_qapp):
"""Call each rl_* method with a None focusWidget."""
self.bridge = readline.ReadlineBridge() self.bridge = readline.ReadlineBridge()
mocked_qapp.focusWidget = mock.Mock(return_value=None)
def test_none(self, qapp):
"""Test if there are no exceptions when the widget is None."""
qapp.focusWidget = mock.Mock(return_value=None)
for name, method in inspect.getmembers(self.bridge, inspect.ismethod): for name, method in inspect.getmembers(self.bridge, inspect.ismethod):
with self.subTest(name=name): if name.startswith('rl_'):
if name.startswith('rl_'): method()
method()
@mock.patch('qutebrowser.misc.readline.QApplication', class TestReadlineBridgeTest:
new_callable=stubs.FakeQApplication)
class ReadlineBridgeTest(unittest.TestCase):
"""Tests for readline bridge.""" """Tests for readline bridge."""
def setUp(self): @pytest.fixture(autouse=True)
def setup(self):
self.qle = mock.Mock() self.qle = mock.Mock()
self.qle.__class__ = QLineEdit self.qle.__class__ = QLineEdit
self.bridge = readline.ReadlineBridge() self.bridge = readline.ReadlineBridge()
@ -64,104 +64,100 @@ class ReadlineBridgeTest(unittest.TestCase):
"""Set the value the fake QLineEdit should return for selectedText.""" """Set the value the fake QLineEdit should return for selectedText."""
self.qle.configure_mock(**{'selectedText.return_value': text}) self.qle.configure_mock(**{'selectedText.return_value': text})
def test_rl_backward_char(self, qapp): def test_rl_backward_char(self, mocked_qapp):
"""Test rl_backward_char.""" """Test rl_backward_char."""
qapp.focusWidget = mock.Mock(return_value=self.qle) mocked_qapp.focusWidget = mock.Mock(return_value=self.qle)
self.bridge.rl_backward_char() self.bridge.rl_backward_char()
self.qle.cursorBackward.assert_called_with(False) self.qle.cursorBackward.assert_called_with(False)
def test_rl_forward_char(self, qapp): def test_rl_forward_char(self, mocked_qapp):
"""Test rl_forward_char.""" """Test rl_forward_char."""
qapp.focusWidget = mock.Mock(return_value=self.qle) mocked_qapp.focusWidget = mock.Mock(return_value=self.qle)
self.bridge.rl_forward_char() self.bridge.rl_forward_char()
self.qle.cursorForward.assert_called_with(False) self.qle.cursorForward.assert_called_with(False)
def test_rl_backward_word(self, qapp): def test_rl_backward_word(self, mocked_qapp):
"""Test rl_backward_word.""" """Test rl_backward_word."""
qapp.focusWidget = mock.Mock(return_value=self.qle) mocked_qapp.focusWidget = mock.Mock(return_value=self.qle)
self.bridge.rl_backward_word() self.bridge.rl_backward_word()
self.qle.cursorWordBackward.assert_called_with(False) self.qle.cursorWordBackward.assert_called_with(False)
def test_rl_forward_word(self, qapp): def test_rl_forward_word(self, mocked_qapp):
"""Test rl_forward_word.""" """Test rl_forward_word."""
qapp.focusWidget = mock.Mock(return_value=self.qle) mocked_qapp.focusWidget = mock.Mock(return_value=self.qle)
self.bridge.rl_forward_word() self.bridge.rl_forward_word()
self.qle.cursorWordForward.assert_called_with(False) self.qle.cursorWordForward.assert_called_with(False)
def test_rl_beginning_of_line(self, qapp): def test_rl_beginning_of_line(self, mocked_qapp):
"""Test rl_beginning_of_line.""" """Test rl_beginning_of_line."""
qapp.focusWidget = mock.Mock(return_value=self.qle) mocked_qapp.focusWidget = mock.Mock(return_value=self.qle)
self.bridge.rl_beginning_of_line() self.bridge.rl_beginning_of_line()
self.qle.home.assert_called_with(False) self.qle.home.assert_called_with(False)
def test_rl_end_of_line(self, qapp): def test_rl_end_of_line(self, mocked_qapp):
"""Test rl_end_of_line.""" """Test rl_end_of_line."""
qapp.focusWidget = mock.Mock(return_value=self.qle) mocked_qapp.focusWidget = mock.Mock(return_value=self.qle)
self.bridge.rl_end_of_line() self.bridge.rl_end_of_line()
self.qle.end.assert_called_with(False) self.qle.end.assert_called_with(False)
def test_rl_delete_char(self, qapp): def test_rl_delete_char(self, mocked_qapp):
"""Test rl_delete_char.""" """Test rl_delete_char."""
qapp.focusWidget = mock.Mock(return_value=self.qle) mocked_qapp.focusWidget = mock.Mock(return_value=self.qle)
self.bridge.rl_delete_char() self.bridge.rl_delete_char()
self.qle.del_.assert_called_with() self.qle.del_.assert_called_with()
def test_rl_backward_delete_char(self, qapp): def test_rl_backward_delete_char(self, mocked_qapp):
"""Test rl_backward_delete_char.""" """Test rl_backward_delete_char."""
qapp.focusWidget = mock.Mock(return_value=self.qle) mocked_qapp.focusWidget = mock.Mock(return_value=self.qle)
self.bridge.rl_backward_delete_char() self.bridge.rl_backward_delete_char()
self.qle.backspace.assert_called_with() self.qle.backspace.assert_called_with()
def test_rl_unix_line_discard(self, qapp): def test_rl_unix_line_discard(self, mocked_qapp):
"""Set a selected text, delete it, see if it comes back with yank.""" """Set a selected text, delete it, see if it comes back with yank."""
qapp.focusWidget = mock.Mock(return_value=self.qle) mocked_qapp.focusWidget = mock.Mock(return_value=self.qle)
self._set_selected_text("delete test") self._set_selected_text("delete test")
self.bridge.rl_unix_line_discard() self.bridge.rl_unix_line_discard()
self.qle.home.assert_called_with(True) self.qle.home.assert_called_with(True)
self.assertEqual(self.bridge._deleted[self.qle], "delete test") assert self.bridge._deleted[self.qle] == "delete test"
self.qle.del_.assert_called_with() self.qle.del_.assert_called_with()
self.bridge.rl_yank() self.bridge.rl_yank()
self.qle.insert.assert_called_with("delete test") self.qle.insert.assert_called_with("delete test")
def test_rl_kill_line(self, qapp): def test_rl_kill_line(self, mocked_qapp):
"""Set a selected text, delete it, see if it comes back with yank.""" """Set a selected text, delete it, see if it comes back with yank."""
qapp.focusWidget = mock.Mock(return_value=self.qle) mocked_qapp.focusWidget = mock.Mock(return_value=self.qle)
self._set_selected_text("delete test") self._set_selected_text("delete test")
self.bridge.rl_kill_line() self.bridge.rl_kill_line()
self.qle.end.assert_called_with(True) self.qle.end.assert_called_with(True)
self.assertEqual(self.bridge._deleted[self.qle], "delete test") assert self.bridge._deleted[self.qle] == "delete test"
self.qle.del_.assert_called_with() self.qle.del_.assert_called_with()
self.bridge.rl_yank() self.bridge.rl_yank()
self.qle.insert.assert_called_with("delete test") self.qle.insert.assert_called_with("delete test")
def test_rl_unix_word_rubout(self, qapp): def test_rl_unix_word_rubout(self, mocked_qapp):
"""Set a selected text, delete it, see if it comes back with yank.""" """Set a selected text, delete it, see if it comes back with yank."""
qapp.focusWidget = mock.Mock(return_value=self.qle) mocked_qapp.focusWidget = mock.Mock(return_value=self.qle)
self._set_selected_text("delete test") self._set_selected_text("delete test")
self.bridge.rl_unix_word_rubout() self.bridge.rl_unix_word_rubout()
self.qle.cursorWordBackward.assert_called_with(True) self.qle.cursorWordBackward.assert_called_with(True)
self.assertEqual(self.bridge._deleted[self.qle], "delete test") assert self.bridge._deleted[self.qle] == "delete test"
self.qle.del_.assert_called_with() self.qle.del_.assert_called_with()
self.bridge.rl_yank() self.bridge.rl_yank()
self.qle.insert.assert_called_with("delete test") self.qle.insert.assert_called_with("delete test")
def test_rl_kill_word(self, qapp): def test_rl_kill_word(self, mocked_qapp):
"""Set a selected text, delete it, see if it comes back with yank.""" """Set a selected text, delete it, see if it comes back with yank."""
qapp.focusWidget = mock.Mock(return_value=self.qle) mocked_qapp.focusWidget = mock.Mock(return_value=self.qle)
self._set_selected_text("delete test") self._set_selected_text("delete test")
self.bridge.rl_kill_word() self.bridge.rl_kill_word()
self.qle.cursorWordForward.assert_called_with(True) self.qle.cursorWordForward.assert_called_with(True)
self.assertEqual(self.bridge._deleted[self.qle], "delete test") assert self.bridge._deleted[self.qle] == "delete test"
self.qle.del_.assert_called_with() self.qle.del_.assert_called_with()
self.bridge.rl_yank() self.bridge.rl_yank()
self.qle.insert.assert_called_with("delete test") self.qle.insert.assert_called_with("delete test")
def test_rl_yank_no_text(self, qapp): def test_rl_yank_no_text(self, mocked_qapp):
"""Test yank without having deleted anything.""" """Test yank without having deleted anything."""
qapp.focusWidget = mock.Mock(return_value=self.qle) mocked_qapp.focusWidget = mock.Mock(return_value=self.qle)
self.bridge.rl_yank() self.bridge.rl_yank()
self.assertFalse(self.qle.insert.called) assert not self.qle.insert.called
if __name__ == '__main__':
unittest.main()

View File

@ -21,6 +21,8 @@
"""Fake objects/stubs.""" """Fake objects/stubs."""
import logging
from unittest import mock from unittest import mock
from PyQt5.QtCore import pyqtSignal, QPoint, QProcess, QObject from PyQt5.QtCore import pyqtSignal, QPoint, QProcess, QObject
@ -37,8 +39,8 @@ class ConfigStub:
data: The config data to return. data: The config data to return.
""" """
def __init__(self, data): def __init__(self, data=None):
self.data = data self.data = data or {}
def section(self, name): def section(self, name):
"""Get a section from the config. """Get a section from the config.
@ -257,7 +259,7 @@ class FakeTimer(QObject):
def setSingleShot(self, singleshot): def setSingleShot(self, singleshot):
self._singleshot = singleshot self._singleshot = singleshot
def singleShot(self): def isSingleShot(self):
return self._singleshot return self._singleshot
def start(self): def start(self):
@ -268,3 +270,20 @@ class FakeTimer(QObject):
def isActive(self): def isActive(self):
return self._started return self._started
class MessageModule:
"""A drop-in replacement for qutebrowser.utils.message."""
def error(self, _win_id, message, _immediately=False):
"""Log an error to the message logger."""
logging.getLogger('message').error(message)
def warning(self, _win_id, message, _immediately=False):
"""Log a warning to the message logger."""
logging.getLogger('message').warning(message)
def info(self, _win_id, message, _immediately=True):
"""Log an info message to the message logger."""
logging.getLogger('message').info(message)

107
tests/test_stubs.py Normal file
View File

@ -0,0 +1,107 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# 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/>.
"""Test test stubs."""
from unittest import mock
import pytest
@pytest.fixture
def timer(stubs):
return stubs.FakeTimer()
def test_timeout(timer):
"""Test whether timeout calls the functions."""
func = mock.Mock()
func2 = mock.Mock()
timer.timeout.connect(func)
timer.timeout.connect(func2)
assert not func.called
assert not func2.called
timer.timeout.emit()
func.assert_called_once_with()
func2.assert_called_once_with()
def test_disconnect_all(timer):
"""Test disconnect without arguments."""
func = mock.Mock()
timer.timeout.connect(func)
timer.timeout.disconnect()
timer.timeout.emit()
assert not func.called
def test_disconnect_one(timer):
"""Test disconnect with a single argument."""
func = mock.Mock()
timer.timeout.connect(func)
timer.timeout.disconnect(func)
timer.timeout.emit()
assert not func.called
def test_disconnect_all_invalid(timer):
"""Test disconnecting with no connections."""
with pytest.raises(TypeError):
timer.timeout.disconnect()
def test_disconnect_one_invalid(timer):
"""Test disconnecting with an invalid connection."""
func1 = mock.Mock()
func2 = mock.Mock()
timer.timeout.connect(func1)
with pytest.raises(TypeError):
timer.timeout.disconnect(func2)
assert not func1.called
assert not func2.called
timer.timeout.emit()
func1.assert_called_once_with()
def test_singleshot(timer):
"""Test setting singleShot."""
assert not timer.isSingleShot()
timer.setSingleShot(True)
assert timer.isSingleShot()
timer.start()
assert timer.isActive()
timer.timeout.emit()
assert not timer.isActive()
def test_active(timer):
"""Test isActive."""
assert not timer.isActive()
timer.start()
assert timer.isActive()
timer.stop()
assert not timer.isActive()
def test_interval(timer):
"""Test setting an interval."""
assert timer.interval() == 0
timer.setInterval(1000)
assert timer.interval() == 1000

View File

@ -42,4 +42,4 @@ def test_log_time(caplog):
assert match assert match
duration = float(match.group(1)) duration = float(match.group(1))
assert 0.09 <= duration <= 0.11 assert 0.08 <= duration <= 0.12

View File

@ -21,7 +21,6 @@
import pytest import pytest
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QStyle, QFrame from PyQt5.QtWidgets import QStyle, QFrame
from qutebrowser.utils import debug from qutebrowser.utils import debug

View File

@ -21,12 +21,11 @@
import pytest import pytest
from qutebrowser.test import stubs
from qutebrowser.utils import debug from qutebrowser.utils import debug
@pytest.fixture @pytest.fixture
def signal(): def signal(stubs):
"""Fixture to provide a faked pyqtSignal.""" """Fixture to provide a faked pyqtSignal."""
return stubs.FakeSignal() return stubs.FakeSignal()

View File

@ -0,0 +1,116 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# 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 qutebrowser.utils.standarddir."""
import os
import os.path
import sys
from PyQt5.QtWidgets import QApplication
import pytest
from qutebrowser.utils import standarddir
@pytest.yield_fixture(autouse=True)
def change_qapp_name():
"""Change the name of the QApplication instance.
This changes the applicationName for all tests in this module to
"qutebrowser_test".
"""
old_name = QApplication.instance().applicationName()
QApplication.instance().setApplicationName('qutebrowser_test')
yield
QApplication.instance().setApplicationName(old_name)
@pytest.mark.skipif(not sys.platform.startswith("linux"),
reason="requires Linux")
class TestGetStandardDirLinux:
"""Tests for standarddir under Linux."""
def test_data_explicit(self, monkeypatch, tmpdir):
"""Test data dir with XDG_DATA_HOME explicitly set."""
monkeypatch.setenv('XDG_DATA_HOME', str(tmpdir))
standarddir.init(None)
assert standarddir.data() == str(tmpdir / 'qutebrowser_test')
def test_config_explicit(self, monkeypatch, tmpdir):
"""Test config dir with XDG_CONFIG_HOME explicitly set."""
monkeypatch.setenv('XDG_CONFIG_HOME', str(tmpdir))
standarddir.init(None)
assert standarddir.config() == str(tmpdir / 'qutebrowser_test')
def test_cache_explicit(self, monkeypatch, tmpdir):
"""Test cache dir with XDG_CACHE_HOME explicitly set."""
monkeypatch.setenv('XDG_CACHE_HOME', str(tmpdir))
standarddir.init(None)
assert standarddir.cache() == str(tmpdir / 'qutebrowser_test')
def test_data(self, monkeypatch, tmpdir):
"""Test data dir with XDG_DATA_HOME not set."""
monkeypatch.setenv('HOME', str(tmpdir))
monkeypatch.delenv('XDG_DATA_HOME', raising=False)
standarddir.init(None)
expected = tmpdir / '.local' / 'share' / 'qutebrowser_test'
assert standarddir.data() == str(expected)
def test_config(self, monkeypatch, tmpdir):
"""Test config dir with XDG_CONFIG_HOME not set."""
monkeypatch.setenv('HOME', str(tmpdir))
monkeypatch.delenv('XDG_CONFIG_HOME', raising=False)
standarddir.init(None)
expected = tmpdir / '.config' / 'qutebrowser_test'
assert standarddir.config() == str(expected)
def test_cache(self, monkeypatch, tmpdir):
"""Test cache dir with XDG_CACHE_HOME not set."""
monkeypatch.setenv('HOME', str(tmpdir))
monkeypatch.delenv('XDG_CACHE_HOME', raising=False)
standarddir.init(None)
expected = tmpdir / '.cache' / 'qutebrowser_test'
assert standarddir.cache() == expected
@pytest.mark.skipif(not sys.platform.startswith("win"),
reason="requires Windows")
class TestGetStandardDirWindows:
"""Tests for standarddir under Windows."""
@pytest.fixture(autouse=True)
def reset_standarddir(self):
standarddir.init(None)
def test_data(self):
"""Test data dir."""
expected = ['qutebrowser_test', 'data']
assert standarddir.data().split(os.sep)[-2:] == expected
def test_config(self):
"""Test config dir."""
assert standarddir.config().split(os.sep)[-1] == 'qutebrowser_test'
def test_cache(self):
"""Test cache dir."""
expected = ['qutebrowser_test', 'cache']
assert standarddir.cache().split(os.sep)[-2:] == expected

View File

@ -21,13 +21,10 @@
"""Tests for qutebrowser.utils.urlutils.""" """Tests for qutebrowser.utils.urlutils."""
import unittest
from unittest import mock
from PyQt5.QtCore import QUrl from PyQt5.QtCore import QUrl
import pytest
from qutebrowser.utils import urlutils from qutebrowser.utils import urlutils
from qutebrowser.test import stubs
def get_config_stub(auto_search=True): def get_config_stub(auto_search=True):
@ -45,7 +42,7 @@ def get_config_stub(auto_search=True):
} }
class SpecialURLTests(unittest.TestCase): class TestSpecialURL:
"""Test is_special_url. """Test is_special_url.
@ -65,65 +62,67 @@ class SpecialURLTests(unittest.TestCase):
'www.qutebrowser.org' 'www.qutebrowser.org'
) )
def test_special_urls(self): @pytest.mark.parametrize('url', SPECIAL_URLS)
def test_special_urls(self, url):
"""Test special URLs.""" """Test special URLs."""
for url in self.SPECIAL_URLS: u = QUrl(url)
with self.subTest(url=url): assert urlutils.is_special_url(u)
u = QUrl(url)
self.assertTrue(urlutils.is_special_url(u))
def test_normal_urls(self): @pytest.mark.parametrize('url', NORMAL_URLS)
def test_normal_urls(self, url):
"""Test non-special URLs.""" """Test non-special URLs."""
for url in self.NORMAL_URLS: u = QUrl(url)
with self.subTest(url=url): assert not urlutils.is_special_url(u)
u = QUrl(url)
self.assertFalse(urlutils.is_special_url(u))
@mock.patch('qutebrowser.utils.urlutils.config', new=stubs.ConfigStub( class TestSearchUrl:
get_config_stub()))
class SearchUrlTests(unittest.TestCase):
"""Test _get_search_url.""" """Test _get_search_url."""
@pytest.fixture(autouse=True)
def mock_config(self, stubs, mocker):
"""Fixture to patch urlutils.config with a stub."""
mocker.patch('qutebrowser.utils.urlutils.config',
new=stubs.ConfigStub(get_config_stub()))
def test_default_engine(self): def test_default_engine(self):
"""Test default search engine.""" """Test default search engine."""
url = urlutils._get_search_url('testfoo') url = urlutils._get_search_url('testfoo')
self.assertEqual(url.host(), 'www.example.com') assert url.host() == 'www.example.com'
self.assertEqual(url.query(), 'q=testfoo') assert url.query() == 'q=testfoo'
def test_engine_pre(self): def test_engine_pre(self):
"""Test search engine name with one word.""" """Test search engine name with one word."""
url = urlutils._get_search_url('test testfoo') url = urlutils._get_search_url('test testfoo')
self.assertEqual(url.host(), 'www.qutebrowser.org') assert url.host() == 'www.qutebrowser.org'
self.assertEqual(url.query(), 'q=testfoo') assert url.query() == 'q=testfoo'
def test_engine_pre_multiple_words(self): def test_engine_pre_multiple_words(self):
"""Test search engine name with multiple words.""" """Test search engine name with multiple words."""
url = urlutils._get_search_url('test testfoo bar foo') url = urlutils._get_search_url('test testfoo bar foo')
self.assertEqual(url.host(), 'www.qutebrowser.org') assert url.host() == 'www.qutebrowser.org'
self.assertEqual(url.query(), 'q=testfoo bar foo') assert url.query() == 'q=testfoo bar foo'
def test_engine_pre_whitespace_at_end(self): def test_engine_pre_whitespace_at_end(self):
"""Test search engine name with one word and whitespace.""" """Test search engine name with one word and whitespace."""
url = urlutils._get_search_url('test testfoo ') url = urlutils._get_search_url('test testfoo ')
self.assertEqual(url.host(), 'www.qutebrowser.org') assert url.host() == 'www.qutebrowser.org'
self.assertEqual(url.query(), 'q=testfoo') assert url.query() == 'q=testfoo'
def test_engine_with_bang_pre(self): def test_engine_with_bang_pre(self):
"""Test search engine with a prepended !bang.""" """Test search engine with a prepended !bang."""
url = urlutils._get_search_url('!python testfoo') url = urlutils._get_search_url('!python testfoo')
self.assertEqual(url.host(), 'www.example.com') assert url.host() == 'www.example.com'
self.assertEqual(url.query(), 'q=%21python testfoo') assert url.query() == 'q=%21python testfoo'
def test_engine_wrong(self): def test_engine_wrong(self):
"""Test with wrong search engine.""" """Test with wrong search engine."""
url = urlutils._get_search_url('blub testfoo') url = urlutils._get_search_url('blub testfoo')
self.assertEqual(url.host(), 'www.example.com') assert url.host() == 'www.example.com'
self.assertEqual(url.query(), 'q=blub testfoo') assert url.query() == 'q=blub testfoo'
class IsUrlTests(unittest.TestCase): class TestIsUrl:
"""Tests for is_url. """Tests for is_url.
@ -158,87 +157,72 @@ class IsUrlTests(unittest.TestCase):
'foo::bar', 'foo::bar',
) )
@mock.patch('qutebrowser.utils.urlutils.config', new=stubs.ConfigStub( @pytest.mark.parametrize('url', URLS)
get_config_stub('naive'))) def test_urls(self, mocker, stubs, url):
def test_urls(self):
"""Test things which are URLs.""" """Test things which are URLs."""
for url in self.URLS: mocker.patch('qutebrowser.utils.urlutils.config', new=stubs.ConfigStub(
with self.subTest(url=url): get_config_stub('naive')))
self.assertTrue(urlutils.is_url(url), url) assert urlutils.is_url(url), url
@mock.patch('qutebrowser.utils.urlutils.config', new=stubs.ConfigStub( @pytest.mark.parametrize('url', NOT_URLS)
get_config_stub('naive'))) def test_not_urls(self, mocker, stubs, url):
def test_not_urls(self):
"""Test things which are not URLs.""" """Test things which are not URLs."""
for url in self.NOT_URLS: mocker.patch('qutebrowser.utils.urlutils.config', new=stubs.ConfigStub(
with self.subTest(url=url): get_config_stub('naive')))
self.assertFalse(urlutils.is_url(url), url) assert not urlutils.is_url(url), url
@mock.patch('qutebrowser.utils.urlutils.config', new=stubs.ConfigStub( @pytest.mark.parametrize('autosearch', [True, False])
get_config_stub(True))) def test_search_autosearch(self, mocker, stubs, autosearch):
def test_search_autosearch(self):
"""Test explicit search with auto-search=True.""" """Test explicit search with auto-search=True."""
self.assertFalse(urlutils.is_url('test foo')) mocker.patch('qutebrowser.utils.urlutils.config', new=stubs.ConfigStub(
get_config_stub(autosearch)))
@mock.patch('qutebrowser.utils.urlutils.config', new=stubs.ConfigStub( assert not urlutils.is_url('test foo')
get_config_stub(False)))
def test_search_no_autosearch(self):
"""Test explicit search with auto-search=False."""
self.assertFalse(urlutils.is_url('test foo'))
class QurlFromUserInputTests(unittest.TestCase): class TestQurlFromUserInput:
"""Tests for qurl_from_user_input.""" """Tests for qurl_from_user_input."""
def test_url(self): def test_url(self):
"""Test a normal URL.""" """Test a normal URL."""
self.assertEqual( url = urlutils.qurl_from_user_input('qutebrowser.org')
urlutils.qurl_from_user_input('qutebrowser.org').toString(), assert url.toString() == 'http://qutebrowser.org'
'http://qutebrowser.org')
def test_url_http(self): def test_url_http(self):
"""Test a normal URL with http://.""" """Test a normal URL with http://."""
self.assertEqual( url = urlutils.qurl_from_user_input('http://qutebrowser.org')
urlutils.qurl_from_user_input('http://qutebrowser.org').toString(), assert url.toString() == 'http://qutebrowser.org'
'http://qutebrowser.org')
def test_ipv6_bare(self): def test_ipv6_bare(self):
"""Test an IPv6 without brackets.""" """Test an IPv6 without brackets."""
self.assertEqual(urlutils.qurl_from_user_input('::1/foo').toString(), url = urlutils.qurl_from_user_input('::1/foo')
'http://[::1]/foo') assert url.toString() == 'http://[::1]/foo'
def test_ipv6(self): def test_ipv6(self):
"""Test an IPv6 with brackets.""" """Test an IPv6 with brackets."""
self.assertEqual(urlutils.qurl_from_user_input('[::1]/foo').toString(), url = urlutils.qurl_from_user_input('[::1]/foo')
'http://[::1]/foo') assert url.toString() == 'http://[::1]/foo'
def test_ipv6_http(self): def test_ipv6_http(self):
"""Test an IPv6 with http:// and brackets.""" """Test an IPv6 with http:// and brackets."""
self.assertEqual( url = urlutils.qurl_from_user_input('http://[::1]')
urlutils.qurl_from_user_input('http://[::1]').toString(), assert url.toString() == 'http://[::1]'
'http://[::1]')
class FilenameFromUrlTests(unittest.TestCase): class TestFilenameFromUrl:
"""Tests for filename_from_url.""" """Tests for filename_from_url."""
def test_invalid_url(self): def test_invalid_url(self):
"""Test with an invalid QUrl.""" """Test with an invalid QUrl."""
self.assertEqual(urlutils.filename_from_url(QUrl()), None) assert urlutils.filename_from_url(QUrl()) is None
def test_url_path(self): def test_url_path(self):
"""Test with an URL with path.""" """Test with an URL with path."""
url = QUrl('http://qutebrowser.org/test.html') url = QUrl('http://qutebrowser.org/test.html')
self.assertEqual(urlutils.filename_from_url(url), 'test.html') assert urlutils.filename_from_url(url) == 'test.html'
def test_url_host(self): def test_url_host(self):
"""Test with an URL with no path.""" """Test with an URL with no path."""
url = QUrl('http://qutebrowser.org/') url = QUrl('http://qutebrowser.org/')
self.assertEqual(urlutils.filename_from_url(url), assert urlutils.filename_from_url(url) == 'qutebrowser.org.html'
'qutebrowser.org.html')
if __name__ == '__main__':
unittest.main()

View File

@ -21,15 +21,14 @@
import sys import sys
import enum import enum
import unittest
import datetime import datetime
import os.path import os.path
from PyQt5.QtCore import Qt from PyQt5.QtCore import Qt
from PyQt5.QtGui import QColor from PyQt5.QtGui import QColor
import pytest
from qutebrowser.utils import utils, qtutils from qutebrowser.utils import utils, qtutils
from qutebrowser.test import helpers
class Color(QColor): class Color(QColor):
@ -42,7 +41,7 @@ class Color(QColor):
alpha=self.alpha()) alpha=self.alpha())
class ElidingTests(unittest.TestCase): class TestEliding:
"""Test elide.""" """Test elide."""
@ -50,33 +49,33 @@ class ElidingTests(unittest.TestCase):
def test_too_small(self): def test_too_small(self):
"""Test eliding to 0 chars which should fail.""" """Test eliding to 0 chars which should fail."""
with self.assertRaises(ValueError): with pytest.raises(ValueError):
utils.elide('foo', 0) utils.elide('foo', 0)
def test_length_one(self): def test_length_one(self):
"""Test eliding to 1 char which should yield ...""" """Test eliding to 1 char which should yield ..."""
self.assertEqual(utils.elide('foo', 1), self.ELLIPSIS) assert utils.elide('foo', 1) == self.ELLIPSIS
def test_fits(self): def test_fits(self):
"""Test eliding with a string which fits exactly.""" """Test eliding with a string which fits exactly."""
self.assertEqual(utils.elide('foo', 3), 'foo') assert utils.elide('foo', 3) == 'foo'
def test_elided(self): def test_elided(self):
"""Test eliding with a string which should get elided.""" """Test eliding with a string which should get elided."""
self.assertEqual(utils.elide('foobar', 3), 'fo' + self.ELLIPSIS) assert utils.elide('foobar', 3) == 'fo' + self.ELLIPSIS
class ReadFileTests(unittest.TestCase): class TestReadFile:
"""Test read_file.""" """Test read_file."""
def test_readfile(self): def test_readfile(self):
"""Read a test file.""" """Read a test file."""
content = utils.read_file(os.path.join('test', 'testfile')) content = utils.read_file(os.path.join('utils', 'testfile'))
self.assertEqual(content.splitlines()[0], "Hello World!") assert content.splitlines()[0] == "Hello World!"
class InterpolateColorTests(unittest.TestCase): class TestInterpolateColor:
"""Tests for interpolate_color. """Tests for interpolate_color.
@ -85,30 +84,31 @@ class InterpolateColorTests(unittest.TestCase):
white: The Color black as a valid Color for tests. white: The Color black as a valid Color for tests.
""" """
def setUp(self): @pytest.fixture(autouse=True)
def setup(self):
self.white = Color('white') self.white = Color('white')
self.black = Color('black') self.black = Color('black')
def test_invalid_start(self): def test_invalid_start(self):
"""Test an invalid start color.""" """Test an invalid start color."""
with self.assertRaises(qtutils.QtValueError): with pytest.raises(qtutils.QtValueError):
utils.interpolate_color(Color(), self.white, 0) utils.interpolate_color(Color(), self.white, 0)
def test_invalid_end(self): def test_invalid_end(self):
"""Test an invalid end color.""" """Test an invalid end color."""
with self.assertRaises(qtutils.QtValueError): with pytest.raises(qtutils.QtValueError):
utils.interpolate_color(self.white, Color(), 0) utils.interpolate_color(self.white, Color(), 0)
def test_invalid_percentage(self): def test_invalid_percentage(self):
"""Test an invalid percentage.""" """Test an invalid percentage."""
with self.assertRaises(ValueError): with pytest.raises(ValueError):
utils.interpolate_color(self.white, self.white, -1) utils.interpolate_color(self.white, self.white, -1)
with self.assertRaises(ValueError): with pytest.raises(ValueError):
utils.interpolate_color(self.white, self.white, 101) utils.interpolate_color(self.white, self.white, 101)
def test_invalid_colorspace(self): def test_invalid_colorspace(self):
"""Test an invalid colorspace.""" """Test an invalid colorspace."""
with self.assertRaises(ValueError): with pytest.raises(ValueError):
utils.interpolate_color(self.white, self.black, 10, QColor.Cmyk) utils.interpolate_color(self.white, self.black, 10, QColor.Cmyk)
def test_valid_percentages_rgb(self): def test_valid_percentages_rgb(self):
@ -116,30 +116,30 @@ class InterpolateColorTests(unittest.TestCase):
white = utils.interpolate_color(self.white, self.black, 0, QColor.Rgb) white = utils.interpolate_color(self.white, self.black, 0, QColor.Rgb)
black = utils.interpolate_color(self.white, self.black, 100, black = utils.interpolate_color(self.white, self.black, 100,
QColor.Rgb) QColor.Rgb)
self.assertEqual(Color(white), self.white) assert Color(white) == self.white
self.assertEqual(Color(black), self.black) assert Color(black) == self.black
def test_valid_percentages_hsv(self): def test_valid_percentages_hsv(self):
"""Test 0% and 100% in the HSV colorspace.""" """Test 0% and 100% in the HSV colorspace."""
white = utils.interpolate_color(self.white, self.black, 0, QColor.Hsv) white = utils.interpolate_color(self.white, self.black, 0, QColor.Hsv)
black = utils.interpolate_color(self.white, self.black, 100, black = utils.interpolate_color(self.white, self.black, 100,
QColor.Hsv) QColor.Hsv)
self.assertEqual(Color(white), self.white) assert Color(white) == self.white
self.assertEqual(Color(black), self.black) assert Color(black) == self.black
def test_valid_percentages_hsl(self): def test_valid_percentages_hsl(self):
"""Test 0% and 100% in the HSL colorspace.""" """Test 0% and 100% in the HSL colorspace."""
white = utils.interpolate_color(self.white, self.black, 0, QColor.Hsl) white = utils.interpolate_color(self.white, self.black, 0, QColor.Hsl)
black = utils.interpolate_color(self.white, self.black, 100, black = utils.interpolate_color(self.white, self.black, 100,
QColor.Hsl) QColor.Hsl)
self.assertEqual(Color(white), self.white) assert Color(white) == self.white
self.assertEqual(Color(black), self.black) assert Color(black) == self.black
def test_interpolation_rgb(self): def test_interpolation_rgb(self):
"""Test an interpolation in the RGB colorspace.""" """Test an interpolation in the RGB colorspace."""
color = utils.interpolate_color(Color(0, 40, 100), Color(0, 20, 200), color = utils.interpolate_color(Color(0, 40, 100), Color(0, 20, 200),
50, QColor.Rgb) 50, QColor.Rgb)
self.assertEqual(Color(color), Color(0, 30, 150)) assert Color(color) == Color(0, 30, 150)
def test_interpolation_hsv(self): def test_interpolation_hsv(self):
"""Test an interpolation in the HSV colorspace.""" """Test an interpolation in the HSV colorspace."""
@ -150,7 +150,7 @@ class InterpolateColorTests(unittest.TestCase):
color = utils.interpolate_color(start, stop, 50, QColor.Hsv) color = utils.interpolate_color(start, stop, 50, QColor.Hsv)
expected = Color() expected = Color()
expected.setHsv(0, 30, 150) expected.setHsv(0, 30, 150)
self.assertEqual(Color(color), expected) assert Color(color) == expected
def test_interpolation_hsl(self): def test_interpolation_hsl(self):
"""Test an interpolation in the HSL colorspace.""" """Test an interpolation in the HSL colorspace."""
@ -161,10 +161,10 @@ class InterpolateColorTests(unittest.TestCase):
color = utils.interpolate_color(start, stop, 50, QColor.Hsl) color = utils.interpolate_color(start, stop, 50, QColor.Hsl)
expected = Color() expected = Color()
expected.setHsl(0, 30, 150) expected.setHsl(0, 30, 150)
self.assertEqual(Color(color), expected) assert Color(color) == expected
class FormatSecondsTests(unittest.TestCase): class TestFormatSeconds:
"""Tests for format_seconds. """Tests for format_seconds.
@ -186,14 +186,13 @@ class FormatSecondsTests(unittest.TestCase):
(36000, '10:00:00'), (36000, '10:00:00'),
] ]
def test_format_seconds(self): @pytest.mark.parametrize('seconds, out', TESTS)
def test_format_seconds(self, seconds, out):
"""Test format_seconds with several tests.""" """Test format_seconds with several tests."""
for seconds, out in self.TESTS: assert utils.format_seconds(seconds) == out
with self.subTest(seconds=seconds):
self.assertEqual(utils.format_seconds(seconds), out)
class FormatTimedeltaTests(unittest.TestCase): class TestFormatTimedelta:
"""Tests for format_timedelta. """Tests for format_timedelta.
@ -217,14 +216,13 @@ class FormatTimedeltaTests(unittest.TestCase):
(datetime.timedelta(seconds=36000), '10h'), (datetime.timedelta(seconds=36000), '10h'),
] ]
def test_format_seconds(self): @pytest.mark.parametrize('td, out', TESTS)
def test_format_seconds(self, td, out):
"""Test format_seconds with several tests.""" """Test format_seconds with several tests."""
for td, out in self.TESTS: assert utils.format_timedelta(td) == out
with self.subTest(td=td):
self.assertEqual(utils.format_timedelta(td), out)
class FormatSizeTests(unittest.TestCase): class TestFormatSize:
"""Tests for format_size. """Tests for format_size.
@ -244,122 +242,117 @@ class FormatSizeTests(unittest.TestCase):
(None, '?.??'), (None, '?.??'),
] ]
def test_format_size(self): KILO_TESTS = [(999, '999.00'), (1000, '1.00k'), (1010, '1.01k')]
@pytest.mark.parametrize('size, out', TESTS)
def test_format_size(self, size, out):
"""Test format_size with several tests.""" """Test format_size with several tests."""
for size, out in self.TESTS: assert utils.format_size(size) == out
with self.subTest(size=size):
self.assertEqual(utils.format_size(size), out)
def test_suffix(self): @pytest.mark.parametrize('size, out', TESTS)
def test_suffix(self, size, out):
"""Test the suffix option.""" """Test the suffix option."""
for size, out in self.TESTS: assert utils.format_size(size, suffix='B') == out + 'B'
with self.subTest(size=size):
self.assertEqual(utils.format_size(size, suffix='B'),
out + 'B')
def test_base(self): @pytest.mark.parametrize('size, out', KILO_TESTS)
def test_base(self, size, out):
"""Test with an alternative base.""" """Test with an alternative base."""
kilo_tests = [(999, '999.00'), (1000, '1.00k'), (1010, '1.01k')] assert utils.format_size(size, base=1000) == out
for size, out in kilo_tests:
with self.subTest(size=size):
self.assertEqual(utils.format_size(size, base=1000), out)
class KeyToStringTests(unittest.TestCase): class TestKeyToString:
"""Test key_to_string.""" """Test key_to_string."""
def test_unicode_garbage_keys(self): def test_unicode_garbage_keys(self):
"""Test a special key where QKeyEvent::toString works incorrectly.""" """Test a special key where QKeyEvent::toString works incorrectly."""
self.assertEqual(utils.key_to_string(Qt.Key_Blue), 'Blue') assert utils.key_to_string(Qt.Key_Blue) == 'Blue'
def test_backtab(self): def test_backtab(self):
"""Test if backtab is normalized to tab correctly.""" """Test if backtab is normalized to tab correctly."""
self.assertEqual(utils.key_to_string(Qt.Key_Backtab), 'Tab') assert utils.key_to_string(Qt.Key_Backtab) == 'Tab'
def test_escape(self): def test_escape(self):
"""Test if escape is normalized to escape correctly.""" """Test if escape is normalized to escape correctly."""
self.assertEqual(utils.key_to_string(Qt.Key_Escape), 'Escape') assert utils.key_to_string(Qt.Key_Escape) == 'Escape'
def test_letter(self): def test_letter(self):
"""Test a simple letter key.""" """Test a simple letter key."""
self.assertEqual(utils.key_to_string(Qt.Key_A), 'A') assert utils.key_to_string(Qt.Key_A) == 'A'
def test_unicode(self): def test_unicode(self):
"""Test a printable unicode key.""" """Test a printable unicode key."""
self.assertEqual(utils.key_to_string(Qt.Key_degree), '°') assert utils.key_to_string(Qt.Key_degree) == '°'
def test_special(self): def test_special(self):
"""Test a non-printable key handled by QKeyEvent::toString.""" """Test a non-printable key handled by QKeyEvent::toString."""
self.assertEqual(utils.key_to_string(Qt.Key_F1), 'F1') assert utils.key_to_string(Qt.Key_F1) == 'F1'
class KeyEventToStringTests(unittest.TestCase): class TestKeyEventToString:
"""Test keyevent_to_string.""" """Test keyevent_to_string."""
def test_only_control(self): def test_only_control(self, fake_keyevent_factory):
"""Test keyevent when only control is pressed.""" """Test keyeevent when only control is pressed."""
evt = helpers.fake_keyevent(key=Qt.Key_Control, evt = fake_keyevent_factory(key=Qt.Key_Control,
modifiers=Qt.ControlModifier) modifiers=Qt.ControlModifier)
self.assertIsNone(utils.keyevent_to_string(evt)) assert utils.keyevent_to_string(evt) is None
def test_only_hyper_l(self): def test_only_hyper_l(self, fake_keyevent_factory):
"""Test keyevent when only Hyper_L is pressed.""" """Test keyeevent when only Hyper_L is pressed."""
evt = helpers.fake_keyevent(key=Qt.Key_Hyper_L, evt = fake_keyevent_factory(key=Qt.Key_Hyper_L,
modifiers=Qt.MetaModifier) modifiers=Qt.MetaModifier)
self.assertIsNone(utils.keyevent_to_string(evt)) assert utils.keyevent_to_string(evt) is None
def test_only_key(self): def test_only_key(self, fake_keyevent_factory):
"""Test with a simple key pressed.""" """Test with a simple key pressed."""
evt = helpers.fake_keyevent(key=Qt.Key_A) evt = fake_keyevent_factory(key=Qt.Key_A)
self.assertEqual(utils.keyevent_to_string(evt), 'A') assert utils.keyevent_to_string(evt) == 'A'
def test_key_and_modifier(self): def test_key_and_modifier(self, fake_keyevent_factory):
"""Test with key and modifier pressed.""" """Test with key and modifier pressed."""
evt = helpers.fake_keyevent(key=Qt.Key_A, modifiers=Qt.ControlModifier) evt = fake_keyevent_factory(key=Qt.Key_A, modifiers=Qt.ControlModifier)
self.assertEqual(utils.keyevent_to_string(evt), 'Ctrl+A') assert utils.keyevent_to_string(evt) == 'Ctrl+A'
def test_key_and_modifiers(self): def test_key_and_modifiers(self, fake_keyevent_factory):
"""Test with key and multiple modifier pressed.""" """Test with key and multiple modifier pressed."""
evt = helpers.fake_keyevent( evt = fake_keyevent_factory(
key=Qt.Key_A, modifiers=(Qt.ControlModifier | Qt.AltModifier | key=Qt.Key_A, modifiers=(Qt.ControlModifier | Qt.AltModifier |
Qt.MetaModifier | Qt.ShiftModifier)) Qt.MetaModifier | Qt.ShiftModifier))
if sys.platform == 'darwin': if sys.platform == 'darwin':
self.assertEqual(utils.keyevent_to_string(evt), assert utils.keyevent_to_string(evt) == 'Ctrl+Alt+Shift+A'
'Ctrl+Alt+Shift+A')
else: else:
self.assertEqual(utils.keyevent_to_string(evt), assert utils.keyevent_to_string(evt) == 'Ctrl+Alt+Meta+Shift+A'
'Ctrl+Alt+Meta+Shift+A')
class NormalizeTests(unittest.TestCase): class TestNormalize:
"""Test normalize_keystr.""" """Test normalize_keystr."""
def test_normalize(self): STRINGS = (
('Control+x', 'ctrl+x'),
('Windows+x', 'meta+x'),
('Mod1+x', 'alt+x'),
('Mod4+x', 'meta+x'),
('Control--', 'ctrl+-'),
('Windows++', 'meta++'),
)
@pytest.mark.parametrize('orig, repl', STRINGS)
def test_normalize(self, orig, repl):
"""Test normalize with some strings.""" """Test normalize with some strings."""
strings = ( assert utils.normalize_keystr(orig) == repl
('Control+x', 'ctrl+x'),
('Windows+x', 'meta+x'),
('Mod1+x', 'alt+x'),
('Mod4+x', 'meta+x'),
('Control--', 'ctrl+-'),
('Windows++', 'meta++'),
)
for orig, repl in strings:
with self.subTest(orig=orig):
self.assertEqual(utils.normalize_keystr(orig), repl)
class IsEnumTests(unittest.TestCase): class TestIsEnum:
"""Test is_enum.""" """Test is_enum."""
def test_enum(self): def test_enum(self):
"""Test is_enum with an enum.""" """Test is_enum with an enum."""
e = enum.Enum('Foo', 'bar, baz') e = enum.Enum('Foo', 'bar, baz')
self.assertTrue(utils.is_enum(e)) assert utils.is_enum(e)
def test_class(self): def test_class(self):
"""Test is_enum with a non-enum class.""" """Test is_enum with a non-enum class."""
@ -368,14 +361,15 @@ class IsEnumTests(unittest.TestCase):
"""Test class for is_enum.""" """Test class for is_enum."""
pass pass
self.assertFalse(utils.is_enum(Test))
assert not utils.is_enum(Test)
def test_object(self): def test_object(self):
"""Test is_enum with a non-enum object.""" """Test is_enum with a non-enum object."""
self.assertFalse(utils.is_enum(23)) assert not utils.is_enum(23)
class RaisesTests(unittest.TestCase): class TestRaises:
"""Test raises.""" """Test raises."""
@ -389,106 +383,98 @@ class RaisesTests(unittest.TestCase):
def test_raises_single_exc_true(self): def test_raises_single_exc_true(self):
"""Test raises with a single exception which gets raised.""" """Test raises with a single exception which gets raised."""
self.assertTrue(utils.raises(ValueError, int, 'a')) assert utils.raises(ValueError, int, 'a')
def test_raises_single_exc_false(self): def test_raises_single_exc_false(self):
"""Test raises with a single exception which does not get raised.""" """Test raises with a single exception which does not get raised."""
self.assertFalse(utils.raises(ValueError, int, '1')) assert not utils.raises(ValueError, int, '1')
def test_raises_multiple_exc_true(self): def test_raises_multiple_exc_true(self):
"""Test raises with multiple exceptions which get raised.""" """Test raises with multiple exceptions which get raised."""
self.assertTrue(utils.raises((ValueError, TypeError), int, 'a')) assert utils.raises((ValueError, TypeError), int, 'a')
self.assertTrue(utils.raises((ValueError, TypeError), int, None)) assert utils.raises((ValueError, TypeError), int, None)
def test_raises_multiple_exc_false(self): def test_raises_multiple_exc_false(self):
"""Test raises with multiple exceptions which do not get raised.""" """Test raises with multiple exceptions which do not get raised."""
self.assertFalse(utils.raises((ValueError, TypeError), int, '1')) assert not utils.raises((ValueError, TypeError), int, '1')
def test_no_args_true(self): def test_no_args_true(self):
"""Test with no args and an exception which gets raised.""" """Test with no args and an exception which gets raised."""
self.assertTrue(utils.raises(Exception, self.do_raise)) assert utils.raises(Exception, self.do_raise)
def test_no_args_false(self): def test_no_args_false(self):
"""Test with no args and an exception which does not get raised.""" """Test with no args and an exception which does not get raised."""
self.assertFalse(utils.raises(Exception, self.do_nothing)) assert not utils.raises(Exception, self.do_nothing)
def test_unrelated_exception(self): def test_unrelated_exception(self):
"""Test with an unrelated exception.""" """Test with an unrelated exception."""
with self.assertRaises(Exception): with pytest.raises(Exception):
utils.raises(ValueError, self.do_raise) utils.raises(ValueError, self.do_raise)
class ForceEncodingTests(unittest.TestCase): class TestForceEncoding:
"""Test force_encoding.""" """Test force_encoding."""
def test_fitting_ascii(self): TESTS = [
"""Test with a text fitting into ASCII.""" ('hello world', 'ascii', 'hello world'),
text = 'hello world' ('hellö wörld', 'utf-8', 'hellö wörld'),
self.assertEqual(utils.force_encoding(text, 'ascii'), text) ('hellö wörld', 'ascii', 'hell? w?rld'),
]
def test_fitting_utf8(self): @pytest.mark.parametrize('inp, enc, expected', TESTS)
"""Test with a text fitting into utf-8.""" def test_fitting_ascii(self, inp, enc, expected):
text = 'hellö wörld' """Test force_encoding will yield expected text."""
self.assertEqual(utils.force_encoding(text, 'utf-8'), text) assert utils.force_encoding(inp, enc) == expected
def test_not_fitting_ascii(self):
"""Test with a text not fitting into ASCII."""
text = 'hellö wörld'
self.assertEqual(utils.force_encoding(text, 'ascii'), 'hell? w?rld')
class NewestSliceTests(unittest.TestCase): class TestNewestSlice:
"""Test newest_slice.""" """Test newest_slice."""
def test_count_minus_two(self): def test_count_minus_two(self):
"""Test with a count of -2.""" """Test with a count of -2."""
with self.assertRaises(ValueError): with pytest.raises(ValueError):
utils.newest_slice([], -2) utils.newest_slice([], -2)
def test_count_minus_one(self): def test_count_minus_one(self):
"""Test with a count of -1 (all elements).""" """Test with a count of -1 (all elements)."""
items = range(20) items = range(20)
sliced = utils.newest_slice(items, -1) sliced = utils.newest_slice(items, -1)
self.assertEqual(list(sliced), list(items)) assert list(sliced) == list(items)
def test_count_zero(self): def test_count_zero(self):
"""Test with a count of 0 (no elements).""" """Test with a count of 0 (no elements)."""
items = range(20) items = range(20)
sliced = utils.newest_slice(items, 0) sliced = utils.newest_slice(items, 0)
self.assertEqual(list(sliced), []) assert list(sliced) == []
def test_count_much_smaller(self): def test_count_much_smaller(self):
"""Test with a count which is much smaller than the iterable.""" """Test with a count which is much smaller than the iterable."""
items = range(20) items = range(20)
sliced = utils.newest_slice(items, 5) sliced = utils.newest_slice(items, 5)
self.assertEqual(list(sliced), [15, 16, 17, 18, 19]) assert list(sliced) == [15, 16, 17, 18, 19]
def test_count_smaller(self): def test_count_smaller(self):
"""Test with a count which is exactly one smaller.""" """Test with a count which is exactly one smaller."""
items = range(5) items = range(5)
sliced = utils.newest_slice(items, 4) sliced = utils.newest_slice(items, 4)
self.assertEqual(list(sliced), [1, 2, 3, 4]) assert list(sliced) == [1, 2, 3, 4]
def test_count_equal(self): def test_count_equal(self):
"""Test with a count which is just as large as the iterable.""" """Test with a count which is just as large as the iterable."""
items = range(5) items = range(5)
sliced = utils.newest_slice(items, 5) sliced = utils.newest_slice(items, 5)
self.assertEqual(list(sliced), list(items)) assert list(sliced) == list(items)
def test_count_bigger(self): def test_count_bigger(self):
"""Test with a count which is one bigger than the iterable.""" """Test with a count which is one bigger than the iterable."""
items = range(5) items = range(5)
sliced = utils.newest_slice(items, 6) sliced = utils.newest_slice(items, 6)
self.assertEqual(list(sliced), list(items)) assert list(sliced) == list(items)
def test_count_much_bigger(self): def test_count_much_bigger(self):
"""Test with a count which is much bigger than the iterable.""" """Test with a count which is much bigger than the iterable."""
items = range(5) items = range(5)
sliced = utils.newest_slice(items, 50) sliced = utils.newest_slice(items, 50)
self.assertEqual(list(sliced), list(items)) assert list(sliced) == list(items)
if __name__ == '__main__':
unittest.main()

14
tox.ini
View File

@ -20,6 +20,8 @@ deps =
py==1.4.26 py==1.4.26
pytest==2.7.0 pytest==2.7.0
pytest-capturelog==0.7 pytest-capturelog==0.7
pytest-qt==1.3.0
pytest-mock==0.4.2
# We don't use {[testenv:mkvenv]commands} here because that seems to be broken # We don't use {[testenv:mkvenv]commands} here because that seems to be broken
# on Ubuntu Trusty. # on Ubuntu Trusty.
commands = commands =
@ -39,8 +41,8 @@ commands =
[testenv:misc] [testenv:misc]
commands = commands =
{envpython} scripts/misc_checks.py git {envpython} scripts/misc_checks.py git
{envpython} scripts/misc_checks.py vcs qutebrowser scripts {envpython} scripts/misc_checks.py vcs qutebrowser scripts tests
{envpython} scripts/misc_checks.py spelling qutebrowser scripts {envpython} scripts/misc_checks.py spelling qutebrowser scripts tests
[testenv:pylint] [testenv:pylint]
skip_install = true skip_install = true
@ -55,6 +57,7 @@ deps =
commands = commands =
{[testenv:mkvenv]commands} {[testenv:mkvenv]commands}
{envdir}/bin/pylint scripts qutebrowser --rcfile=.pylintrc --output-format=colorized --reports=no {envdir}/bin/pylint scripts qutebrowser --rcfile=.pylintrc --output-format=colorized --reports=no
{envpython} scripts/run_pylint_on_tests.py --rcfile=.pylintrc --output-format=colorized --reports=no
[testenv:pep257] [testenv:pep257]
skip_install = true skip_install = true
@ -63,7 +66,7 @@ deps = pep257==0.5.0
# D102: Docstring missing, will be handled by others # D102: Docstring missing, will be handled by others
# D209: Blank line before closing """ (removed from PEP257) # D209: Blank line before closing """ (removed from PEP257)
# D402: First line should not be function's signature (false-positives) # D402: First line should not be function's signature (false-positives)
commands = {envpython} -m pep257 scripts qutebrowser --ignore=D102,D209,D402 '--match=(?!resources|test_content_disposition).*\.py' commands = {envpython} -m pep257 scripts tests qutebrowser --ignore=D102,D103,D209,D402 '--match=(?!resources|test_content_disposition).*\.py'
[testenv:flake8] [testenv:flake8]
skip_install = true skip_install = true
@ -74,7 +77,7 @@ deps =
flake8==2.4.0 flake8==2.4.0
commands = commands =
{[testenv:mkvenv]commands} {[testenv:mkvenv]commands}
{envdir}/bin/flake8 scripts qutebrowser --config=.flake8 {envdir}/bin/flake8 scripts tests qutebrowser --config=.flake8
[testenv:pyroma] [testenv:pyroma]
skip_install = true skip_install = true
@ -103,3 +106,6 @@ commands =
{envpython} scripts/src2asciidoc.py {envpython} scripts/src2asciidoc.py
git --no-pager diff --exit-code --stat git --no-pager diff --exit-code --stat
{envpython} scripts/asciidoc2html.py {posargs} {envpython} scripts/asciidoc2html.py {posargs}
[pytest]
norecursedirs = .tox .venv