Squashed commit of the following:
Updated the supported python versions list.
Fixed regression in displaying xposts. #173.
Fixing a few style things.
Added a more robust test for the tornado handler.
Trying without pytest-cov
Updated travis for coverage.
Remove python 3.2 support because no unicode literals, following what praw supports.
"Side effect is not iterable."
Added requirements for travis.
Renamed travis file correctly.
Adding test configurations, got tox working.
Adding vcr cassettes to the repo.
Renamed requirements files.
Split up tests and cleaned up test names.
Tests done, still one failure.
Treat cassettes as binary to prevent bad merging.
Fixed a few broken tests.
Added a timeout to notifications.
Prepping subreddit page.
Finished submission page tests.
Working on submission tests.
Fixed vcr matching on urls with params, started submission tests.
Log cleanup.
Still trying to fix a broken test.
-Fixed a few pytest bugs and tweaked logging.
Still working on subscription tests.
Finished page tests, on to subscription page.
Finished content tests and starting page tests.
Added the test refresh-token file to gitignore.
Moved functional test file out of the repository.
Continuing work on subreddit content tests.
Tests now match module names, cassettes are split into individual tests for faster loading.
Linter fixes.
Cleanup.
Added support for nested loaders.
Added pytest options, starting subreddit content tests.
Back on track with loader, continuing content tests.
Finishing submission content tests and discovered snag with loader exception handling.
VCR up and running, continuing to implement content tests.
Playing around with vcr.py
Moved helper functions into terminal and new objects.py
Fixed a few broken tests.
Working on navigator tests.
Reorganizing some things.
Mocked webbrowser._tryorder for terminal test.
Completed oauth tests.
Progress on the oauth tests.
Working on adding fake tornado request.
Starting on OAuth tool tests.
Finished curses helpers tests.
Still working on curses helpers tests.
Almost finished with tests on curses helpers.
Adding tests and working on mocking stdscr.
Starting to add tests for curses functions.
Merge branch 'future_work' of https://github.com/michael-lazar/rtv into future_work
Refactoring controller, still in progress.
Renamed auth handler.
Rename CursesHelper to CursesBase.
Added temporary file with a possible template for func testing.
Mixup between basename and dirname.
Merge branch 'future_work' of https://github.com/michael-lazar/rtv into future_work
py3 compatability for mock.
Beginning to refactor the curses session.
Started adding tests, improved unicode handling in the config.
Cleanup, fixed a few typos.
Major refactor, almost done!.
Started a config class.
Merge branch 'master' into future_work
The editor now handles unicode characters in all situations.
Fixed a few typos from previous commits.
__main__.py formatting.
Cleaned up history logic and moved to the config file.
This commit is contained in:
443
tests/test_objects.py
Normal file
443
tests/test_objects.py
Normal file
@@ -0,0 +1,443 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import time
|
||||
import curses
|
||||
|
||||
import pytest
|
||||
import requests
|
||||
|
||||
from rtv.objects import Color, Controller, Navigator, curses_session
|
||||
|
||||
try:
|
||||
from unittest import mock
|
||||
except ImportError:
|
||||
import mock
|
||||
|
||||
|
||||
@pytest.mark.parametrize('ascii', [True, False])
|
||||
def test_objects_load_screen(terminal, stdscr, ascii):
|
||||
terminal.ascii = ascii
|
||||
|
||||
# Ensure the thread is properly started/stopped
|
||||
with terminal.loader(delay=0, message=u'Hello', trail=u'...'):
|
||||
assert terminal.loader._animator.is_alive()
|
||||
assert not terminal.loader._is_running
|
||||
assert not terminal.loader._animator.is_alive()
|
||||
assert terminal.loader.exception is None
|
||||
assert stdscr.subwin.ncols == 10
|
||||
assert stdscr.subwin.nlines == 3
|
||||
|
||||
|
||||
@pytest.mark.parametrize('ascii', [True, False])
|
||||
def test_objects_load_screen_exception_unhandled(terminal, stdscr, ascii):
|
||||
terminal.ascii = ascii
|
||||
|
||||
# Raising an exception should clean up the loader properly
|
||||
with pytest.raises(Exception):
|
||||
with terminal.loader(delay=0):
|
||||
assert terminal.loader._animator.is_alive()
|
||||
raise Exception()
|
||||
assert not terminal.loader._is_running
|
||||
assert not terminal.loader._animator.is_alive()
|
||||
|
||||
|
||||
@pytest.mark.parametrize('ascii', [True, False])
|
||||
def test_objects_load_screen_exception_handled(terminal, stdscr, ascii):
|
||||
terminal.ascii = ascii
|
||||
|
||||
# Raising a handled exception should get stored on the loaders
|
||||
with terminal.loader(delay=0):
|
||||
assert terminal.loader._animator.is_alive()
|
||||
raise requests.ConnectionError()
|
||||
assert not terminal.loader._is_running
|
||||
assert not terminal.loader._animator.is_alive()
|
||||
assert isinstance(terminal.loader.exception, requests.ConnectionError)
|
||||
error_message = 'Connection Error'.encode('ascii' if ascii else 'utf-8')
|
||||
stdscr.subwin.addstr.assert_called_with(1, 1, error_message)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('ascii', [True, False])
|
||||
def test_objects_load_screen_exception_not_caught(terminal, stdscr, ascii):
|
||||
terminal.ascii = ascii
|
||||
|
||||
with pytest.raises(KeyboardInterrupt):
|
||||
with terminal.loader(delay=0, catch_exception=False):
|
||||
assert terminal.loader._animator.is_alive()
|
||||
raise KeyboardInterrupt()
|
||||
assert not terminal.loader._is_running
|
||||
assert not terminal.loader._animator.is_alive()
|
||||
assert terminal.loader.exception is None
|
||||
|
||||
|
||||
@pytest.mark.parametrize('ascii', [True, False])
|
||||
def test_objects_load_screen_keyboard_interrupt(terminal, stdscr, ascii):
|
||||
terminal.ascii = ascii
|
||||
|
||||
# Raising a KeyboardInterrupt should be also be stored
|
||||
with terminal.loader(delay=0):
|
||||
assert terminal.loader._animator.is_alive()
|
||||
raise KeyboardInterrupt()
|
||||
assert not terminal.loader._is_running
|
||||
assert not terminal.loader._animator.is_alive()
|
||||
assert isinstance(terminal.loader.exception, KeyboardInterrupt)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('ascii', [True, False])
|
||||
def test_objects_load_screen_escape(terminal, stdscr, ascii):
|
||||
terminal.ascii = ascii
|
||||
|
||||
stdscr.getch.return_value = terminal.ESCAPE
|
||||
|
||||
# Pressing escape should trigger an interrupt during the delay section
|
||||
with mock.patch('os.kill') as kill:
|
||||
with terminal.loader():
|
||||
time.sleep(0.1)
|
||||
assert not terminal.loader._is_running
|
||||
assert not terminal.loader._animator.is_alive()
|
||||
assert kill.called
|
||||
|
||||
# As will as during the animation section
|
||||
with mock.patch('os.kill') as kill:
|
||||
with terminal.loader(delay=0):
|
||||
time.sleep(0.1)
|
||||
assert not terminal.loader._is_running
|
||||
assert not terminal.loader._animator.is_alive()
|
||||
assert kill.called
|
||||
|
||||
|
||||
@pytest.mark.parametrize('ascii', [True, False])
|
||||
def test_objects_load_screen_initial_delay(terminal, stdscr, ascii):
|
||||
terminal.ascii = ascii
|
||||
|
||||
# If we don't reach the initial delay nothing should be drawn
|
||||
with terminal.loader(delay=0.1):
|
||||
time.sleep(0.05)
|
||||
assert not stdscr.subwin.addstr.called
|
||||
|
||||
|
||||
@pytest.mark.parametrize('ascii', [True, False])
|
||||
def test_objects_load_screen_nested(terminal, ascii):
|
||||
terminal.ascii = ascii
|
||||
|
||||
with terminal.loader(message='Outer'):
|
||||
with terminal.loader(message='Inner'):
|
||||
raise requests.ConnectionError()
|
||||
assert False # Should never be reached
|
||||
|
||||
assert isinstance(terminal.loader.exception, requests.ConnectionError)
|
||||
assert terminal.loader.depth == 0
|
||||
assert not terminal.loader._is_running
|
||||
assert not terminal.loader._animator.is_alive()
|
||||
|
||||
|
||||
@pytest.mark.parametrize('ascii', [True, False])
|
||||
def test_objects_load_screen_nested_complex(terminal, stdscr, ascii):
|
||||
terminal.ascii = ascii
|
||||
|
||||
with terminal.loader(message='Outer') as outer_loader:
|
||||
assert outer_loader.depth == 1
|
||||
|
||||
with terminal.loader(message='Inner') as inner_loader:
|
||||
assert inner_loader.depth == 2
|
||||
assert inner_loader._args[2] == 'Outer'
|
||||
|
||||
with terminal.loader():
|
||||
assert terminal.loader.depth == 2
|
||||
raise requests.ConnectionError()
|
||||
|
||||
assert False # Should never be reached
|
||||
|
||||
assert isinstance(terminal.loader.exception, requests.ConnectionError)
|
||||
assert terminal.loader.depth == 0
|
||||
assert not terminal.loader._is_running
|
||||
assert not terminal.loader._animator.is_alive()
|
||||
error_message = 'Connection Error'.encode('ascii' if ascii else 'utf-8')
|
||||
stdscr.subwin.addstr.assert_called_once_with(1, 1, error_message)
|
||||
|
||||
|
||||
def test_objects_color(stdscr):
|
||||
|
||||
colors = ['RED', 'GREEN', 'YELLOW', 'BLUE', 'MAGENTA', 'CYAN', 'WHITE']
|
||||
|
||||
# Check that all colors start with the default value
|
||||
for color in colors:
|
||||
assert getattr(Color, color) == curses.A_NORMAL
|
||||
|
||||
Color.init()
|
||||
assert curses.use_default_colors.called
|
||||
|
||||
# Check that all colors are populated
|
||||
for color in colors:
|
||||
assert getattr(Color, color) == 23
|
||||
|
||||
|
||||
def test_objects_curses_session(stdscr):
|
||||
|
||||
# Normal setup and cleanup
|
||||
with curses_session():
|
||||
pass
|
||||
assert curses.initscr.called
|
||||
assert curses.endwin.called
|
||||
curses.initscr.reset_mock()
|
||||
curses.endwin.reset_mock()
|
||||
|
||||
# Ensure cleanup runs if an error occurs
|
||||
with pytest.raises(KeyboardInterrupt):
|
||||
with curses_session():
|
||||
raise KeyboardInterrupt()
|
||||
assert curses.initscr.called
|
||||
assert curses.endwin.called
|
||||
curses.initscr.reset_mock()
|
||||
curses.endwin.reset_mock()
|
||||
|
||||
# But cleanup shouldn't run if stdscr was never instantiated
|
||||
curses.initscr.side_effect = KeyboardInterrupt
|
||||
with pytest.raises(KeyboardInterrupt):
|
||||
with curses_session():
|
||||
pass
|
||||
assert curses.initscr.called
|
||||
assert not curses.endwin.called
|
||||
curses.initscr.reset_mock()
|
||||
curses.endwin.reset_mock()
|
||||
|
||||
|
||||
def test_objects_controller():
|
||||
|
||||
class ControllerA(Controller):
|
||||
character_map = {}
|
||||
|
||||
class ControllerB(ControllerA):
|
||||
character_map = {}
|
||||
|
||||
class ControllerC(ControllerA):
|
||||
character_map = {}
|
||||
|
||||
@ControllerA.register('1')
|
||||
def call_page(_):
|
||||
return 'a1'
|
||||
|
||||
@ControllerA.register('2')
|
||||
def call_page(_):
|
||||
return 'a2'
|
||||
|
||||
@ControllerB.register('1')
|
||||
def call_page(_):
|
||||
return 'b1'
|
||||
|
||||
@ControllerC.register('2')
|
||||
def call_page(_):
|
||||
return 'c2'
|
||||
|
||||
controller_a = ControllerA(None)
|
||||
controller_b = ControllerB(None)
|
||||
controller_c = ControllerC(None)
|
||||
|
||||
assert controller_a.trigger('1') == 'a1'
|
||||
assert controller_a.trigger('2') == 'a2'
|
||||
assert controller_a.trigger('3') is None
|
||||
|
||||
assert controller_b.trigger('1') == 'b1'
|
||||
assert controller_b.trigger('2') == 'a2'
|
||||
assert controller_b.trigger('3') is None
|
||||
|
||||
assert controller_c.trigger('1') == 'a1'
|
||||
assert controller_c.trigger('2') == 'c2'
|
||||
assert controller_c.trigger('3') is None
|
||||
|
||||
|
||||
def test_objects_navigator_properties():
|
||||
|
||||
def valid_page_cb(_):
|
||||
return
|
||||
|
||||
nav = Navigator(valid_page_cb)
|
||||
assert nav.step == 1
|
||||
assert nav.position == (0, 0, False)
|
||||
assert nav.absolute_index == 0
|
||||
|
||||
nav = Navigator(valid_page_cb, 5, 2, True)
|
||||
assert nav.step == -1
|
||||
assert nav.position == (5, 2, True)
|
||||
assert nav.absolute_index == 3
|
||||
|
||||
|
||||
def test_objects_navigator_move():
|
||||
|
||||
def valid_page_cb(index):
|
||||
if index < 0 or index > 3:
|
||||
raise IndexError()
|
||||
|
||||
nav = Navigator(valid_page_cb)
|
||||
|
||||
# Try to scroll up past the first item
|
||||
valid, redraw = nav.move(-1, 2)
|
||||
assert not valid
|
||||
assert not redraw
|
||||
|
||||
# Scroll down
|
||||
valid, redraw = nav.move(1, 3)
|
||||
assert nav.page_index == 0
|
||||
assert nav.cursor_index == 1
|
||||
assert valid
|
||||
assert not redraw
|
||||
|
||||
# Scroll down, reach last item on the page and flip the screen
|
||||
valid, redraw = nav.move(1, 3)
|
||||
assert nav.page_index == 2
|
||||
assert nav.cursor_index == 0
|
||||
assert nav.inverted
|
||||
assert valid
|
||||
assert redraw
|
||||
|
||||
# Keep scrolling
|
||||
valid, redraw = nav.move(1, 3)
|
||||
assert nav.page_index == 3
|
||||
assert nav.cursor_index == 0
|
||||
assert nav.inverted
|
||||
assert valid
|
||||
assert redraw
|
||||
|
||||
# Reach the end of the page and stop
|
||||
valid, redraw = nav.move(1, 1)
|
||||
assert nav.page_index == 3
|
||||
assert nav.cursor_index == 0
|
||||
assert nav.inverted
|
||||
assert not valid
|
||||
assert not redraw
|
||||
|
||||
# Last item was large and takes up the whole screen, scroll back up and
|
||||
# flip the screen again
|
||||
valid, redraw = nav.move(-1, 1)
|
||||
assert nav.page_index == 2
|
||||
assert nav.cursor_index == 0
|
||||
assert not nav.inverted
|
||||
assert valid
|
||||
assert redraw
|
||||
|
||||
|
||||
def test_objects_navigator_move_new_submission():
|
||||
|
||||
def valid_page_cb(index):
|
||||
if index != -1:
|
||||
raise IndexError()
|
||||
|
||||
nav = Navigator(valid_page_cb, page_index=-1)
|
||||
|
||||
# Can't move up
|
||||
valid, redraw = nav.move(-1, 1)
|
||||
assert nav.page_index == -1
|
||||
assert nav.cursor_index == 0
|
||||
assert not nav.inverted
|
||||
assert not valid
|
||||
assert not redraw
|
||||
|
||||
# Can't move down
|
||||
valid, redraw = nav.move(1, 1)
|
||||
assert nav.page_index == -1
|
||||
assert nav.cursor_index == 0
|
||||
assert not nav.inverted
|
||||
assert not valid
|
||||
assert not redraw
|
||||
|
||||
|
||||
def test_objects_navigator_move_submission():
|
||||
|
||||
def valid_page_cb(index):
|
||||
if index < -1 or index > 4:
|
||||
raise IndexError()
|
||||
|
||||
nav = Navigator(valid_page_cb, page_index=-1)
|
||||
|
||||
# Can't move up
|
||||
valid, redraw = nav.move(-1, 2)
|
||||
assert nav.page_index == -1
|
||||
assert nav.cursor_index == 0
|
||||
assert not nav.inverted
|
||||
assert not valid
|
||||
assert not redraw
|
||||
|
||||
# Moving down jumps to the first comment
|
||||
valid, redraw = nav.move(1, 2)
|
||||
assert nav.page_index == 0
|
||||
assert nav.cursor_index == 0
|
||||
assert not nav.inverted
|
||||
assert valid
|
||||
assert redraw
|
||||
|
||||
# Moving down again inverts the screen
|
||||
valid, redraw = nav.move(1, 2)
|
||||
assert nav.page_index == 1
|
||||
assert nav.cursor_index == 0
|
||||
assert nav.inverted
|
||||
assert valid
|
||||
assert redraw
|
||||
|
||||
# Move up to the first comment
|
||||
valid, redraw = nav.move(-1, 2)
|
||||
assert nav.page_index == 0
|
||||
assert nav.cursor_index == 0
|
||||
assert not nav.inverted
|
||||
assert valid
|
||||
assert redraw
|
||||
|
||||
# Move up to the submission
|
||||
valid, redraw = nav.move(-1, 2)
|
||||
assert nav.page_index == -1
|
||||
assert nav.cursor_index == 0
|
||||
assert not nav.inverted
|
||||
assert valid
|
||||
assert redraw
|
||||
|
||||
|
||||
@pytest.mark.xfail(reason="Paging is still broken in several edge-cases")
|
||||
def test_objects_navigator_move_page():
|
||||
|
||||
def valid_page_cb(index):
|
||||
if index < 0 or index > 7:
|
||||
raise IndexError()
|
||||
|
||||
nav = Navigator(valid_page_cb, cursor_index=2)
|
||||
|
||||
# Can't move up
|
||||
valid, redraw = nav.move_page(-1, 5)
|
||||
assert nav.page_index == 0
|
||||
assert nav.cursor_index == 0
|
||||
assert not nav.inverted
|
||||
assert not valid
|
||||
assert not redraw
|
||||
|
||||
# Page down
|
||||
valid, redraw = nav.move_page(1, 5)
|
||||
assert nav.page_index == 4
|
||||
assert nav.cursor_index == 0
|
||||
assert nav.inverted
|
||||
assert valid
|
||||
assert redraw
|
||||
|
||||
# Page up
|
||||
valid, redraw = nav.move_page(-1, 3)
|
||||
assert nav.page_index == 2
|
||||
assert nav.cursor_index == 0
|
||||
assert not nav.inverted
|
||||
assert valid
|
||||
assert redraw
|
||||
|
||||
|
||||
def test_objects_navigator_flip():
|
||||
|
||||
def valid_page_cb(index):
|
||||
if index < 0 or index > 10:
|
||||
raise IndexError()
|
||||
|
||||
nav = Navigator(valid_page_cb)
|
||||
|
||||
nav.flip(5)
|
||||
assert nav.page_index == 5
|
||||
assert nav.cursor_index == 5
|
||||
assert nav.inverted
|
||||
|
||||
nav.flip(3)
|
||||
assert nav.page_index == 2
|
||||
assert nav.cursor_index == 3
|
||||
assert not nav.inverted
|
||||
Reference in New Issue
Block a user