545 lines
18 KiB
Python
545 lines
18 KiB
Python
# -*- coding: utf-8 -*-
|
|
from __future__ import unicode_literals
|
|
|
|
import curses
|
|
from collections import OrderedDict
|
|
|
|
import pytest
|
|
|
|
from tuir.submission_page import SubmissionPage
|
|
from tuir.docs import FOOTER_SUBMISSION
|
|
|
|
try:
|
|
from unittest import mock
|
|
except ImportError:
|
|
import mock
|
|
|
|
|
|
PROMPTS = OrderedDict([
|
|
('prompt_1', 'comments/571dw3'),
|
|
('prompt_2', '///comments/571dw3'),
|
|
('prompt_3', '/comments/571dw3'),
|
|
('prompt_4', '/r/pics/comments/571dw3/'),
|
|
('prompt_5', 'https://www.reddit.com/r/pics/comments/571dw3/at_disneyland'),
|
|
])
|
|
|
|
|
|
def test_submission_page_construct(reddit, terminal, config, oauth):
|
|
window = terminal.stdscr.subwin
|
|
url = ('https://www.reddit.com/r/Python/comments/2xmo63/'
|
|
'a_python_terminal_viewer_for_browsing_reddit')
|
|
|
|
with terminal.loader():
|
|
page = SubmissionPage(reddit, terminal, config, oauth, url=url)
|
|
assert terminal.loader.exception is None
|
|
|
|
# Toggle the second comment so we can check the draw more comments method
|
|
page.content.toggle(1)
|
|
|
|
# Set some special flags to make sure that we can draw them
|
|
submission_data = page.content.get(-1)
|
|
submission_data['gold'] = 1
|
|
submission_data['stickied'] = True
|
|
submission_data['saved'] = True
|
|
submission_data['flair'] = 'flair'
|
|
|
|
# Set some special flags to make sure that we can draw them
|
|
comment_data = page.content.get(0)
|
|
comment_data['gold'] = 3
|
|
comment_data['stickied'] = True
|
|
comment_data['saved'] = True
|
|
comment_data['flair'] = 'flair'
|
|
|
|
page.draw()
|
|
|
|
# Title
|
|
title = url[:terminal.stdscr.ncols-1].encode('utf-8')
|
|
window.addstr.assert_any_call(0, 0, title)
|
|
|
|
# Banner
|
|
menu = '[1]hot [2]top [3]rising [4]new [5]controversial'
|
|
window.addstr.assert_any_call(0, 0, menu.encode('utf-8'))
|
|
|
|
# Footer - The text is longer than the default terminal width
|
|
text = FOOTER_SUBMISSION.strip()[:79]
|
|
window.addstr.assert_any_call(0, 0, text.encode('utf-8'))
|
|
|
|
# Submission
|
|
submission_data = page.content.get(-1)
|
|
text = submission_data['title'].encode('utf-8')
|
|
window.subwin.addstr.assert_any_call(1, 1, text, 2097152)
|
|
assert window.subwin.border.called
|
|
|
|
# Comment
|
|
comment_data = page.content.get(0)
|
|
text = comment_data['split_body'][0].encode('utf-8')
|
|
window.subwin.addstr.assert_any_call(1, 1, text, curses.A_NORMAL)
|
|
|
|
# More Comments
|
|
comment_data = page.content.get(1)
|
|
text = comment_data['body'].encode('utf-8')
|
|
window.subwin.addstr.assert_any_call(0, 1, text, curses.A_NORMAL)
|
|
|
|
# Cursor should not be drawn when the page is first opened
|
|
assert not any(args[0][3] == curses.A_REVERSE
|
|
for args in window.subwin.addch.call_args_list)
|
|
|
|
# Reload with a smaller terminal window
|
|
terminal.stdscr.ncols = 20
|
|
terminal.stdscr.nlines = 10
|
|
with terminal.loader():
|
|
page = SubmissionPage(reddit, terminal, config, oauth, url=url)
|
|
assert terminal.loader.exception is None
|
|
page.draw()
|
|
|
|
|
|
def test_submission_refresh(submission_page):
|
|
|
|
# Should be able to refresh content
|
|
submission_page.refresh_content()
|
|
|
|
|
|
def test_submission_exit(submission_page):
|
|
|
|
# Exiting should set active to false
|
|
submission_page.active = True
|
|
submission_page.controller.trigger('h')
|
|
assert not submission_page.active
|
|
|
|
|
|
def test_submission_unauthenticated(submission_page, terminal):
|
|
|
|
# Unauthenticated commands
|
|
methods = [
|
|
'a', # Upvote
|
|
'z', # Downvote
|
|
'c', # Comment
|
|
'e', # Edit
|
|
'd', # Delete
|
|
'w', # Save
|
|
]
|
|
for ch in methods:
|
|
submission_page.controller.trigger(ch)
|
|
text = 'Not logged in'.encode('utf-8')
|
|
terminal.stdscr.subwin.addstr.assert_called_with(1, 1, text)
|
|
|
|
|
|
def test_submission_open(submission_page, terminal):
|
|
|
|
# Open the selected link with the web browser
|
|
with mock.patch.object(terminal, 'open_browser'):
|
|
submission_page.controller.trigger(terminal.RETURN)
|
|
assert terminal.open_browser.called
|
|
|
|
|
|
def test_submission_prompt(submission_page, terminal):
|
|
|
|
# Prompt for a different subreddit
|
|
with mock.patch.object(terminal, 'prompt_input'):
|
|
# Valid input
|
|
submission_page.active = True
|
|
submission_page.selected_page = None
|
|
terminal.prompt_input.return_value = 'front/top'
|
|
submission_page.controller.trigger('/')
|
|
|
|
submission_page.handle_selected_page()
|
|
assert not submission_page.active
|
|
assert submission_page.selected_page
|
|
|
|
# Invalid input
|
|
submission_page.active = True
|
|
submission_page.selected_page = None
|
|
terminal.prompt_input.return_value = 'front/pot'
|
|
submission_page.controller.trigger('/')
|
|
|
|
submission_page.handle_selected_page()
|
|
assert submission_page.active
|
|
assert not submission_page.selected_page
|
|
|
|
|
|
@pytest.mark.parametrize('prompt', PROMPTS.values(), ids=list(PROMPTS))
|
|
def test_submission_prompt_submission(submission_page, terminal, prompt):
|
|
|
|
# Navigate to a different submission from inside a submission
|
|
with mock.patch.object(terminal, 'prompt_input'):
|
|
terminal.prompt_input.return_value = prompt
|
|
submission_page.content.order = 'top'
|
|
submission_page.controller.trigger('/')
|
|
assert not terminal.loader.exception
|
|
|
|
submission_page.handle_selected_page()
|
|
assert not submission_page.active
|
|
assert submission_page.selected_page
|
|
|
|
assert submission_page.selected_page.content.order is None
|
|
data = submission_page.selected_page.content.get(-1)
|
|
assert data['object'].id == '571dw3'
|
|
|
|
|
|
def test_submission_order(submission_page):
|
|
|
|
submission_page.controller.trigger('1')
|
|
assert submission_page.content.order == 'hot'
|
|
submission_page.controller.trigger('2')
|
|
assert submission_page.content.order == 'top'
|
|
submission_page.controller.trigger('3')
|
|
assert submission_page.content.order == 'rising'
|
|
submission_page.controller.trigger('4')
|
|
assert submission_page.content.order == 'new'
|
|
submission_page.controller.trigger('5')
|
|
assert submission_page.content.order == 'controversial'
|
|
|
|
# Shouldn't be able to sort the submission page by gilded
|
|
submission_page.controller.trigger('6')
|
|
assert submission_page.content.order == 'controversial'
|
|
|
|
|
|
def test_submission_move_top_bottom(submission_page):
|
|
|
|
submission_page.controller.trigger('G')
|
|
assert submission_page.nav.absolute_index == 44
|
|
|
|
submission_page.controller.trigger('g')
|
|
submission_page.controller.trigger('g')
|
|
assert submission_page.nav.absolute_index == -1
|
|
|
|
|
|
def test_submission_move_sibling_parent(submission_page):
|
|
|
|
# Jump to sibling
|
|
with mock.patch.object(submission_page, 'clear_input_queue'):
|
|
submission_page.controller.trigger('j')
|
|
submission_page.controller.trigger('J')
|
|
assert submission_page.nav.absolute_index == 7
|
|
|
|
# Jump to parent
|
|
with mock.patch.object(submission_page, 'clear_input_queue'):
|
|
submission_page.controller.trigger('k')
|
|
submission_page.controller.trigger('k')
|
|
submission_page.controller.trigger('K')
|
|
assert submission_page.nav.absolute_index == 0
|
|
|
|
|
|
def test_submission_pager(submission_page, terminal):
|
|
|
|
# View a submission with the pager
|
|
with mock.patch.object(terminal, 'open_pager'):
|
|
submission_page.controller.trigger('l')
|
|
assert terminal.open_pager.called
|
|
|
|
# Move down to the first comment
|
|
with mock.patch.object(submission_page, 'clear_input_queue'):
|
|
submission_page.controller.trigger('j')
|
|
|
|
# View a comment with the pager
|
|
with mock.patch.object(terminal, 'open_pager'):
|
|
submission_page.controller.trigger('l')
|
|
assert terminal.open_pager.called
|
|
|
|
|
|
def test_submission_comment_not_enough_space(submission_page, terminal):
|
|
|
|
# The first comment is 10 lines, shrink the screen so that it won't fit.
|
|
# Setting the terminal to 10 lines means that there will only be 8 lines
|
|
# available (after subtracting the header and footer) to draw the comment.
|
|
terminal.stdscr.nlines = 10
|
|
|
|
# Select the first comment
|
|
with mock.patch.object(submission_page, 'clear_input_queue'):
|
|
submission_page.move_cursor_down()
|
|
|
|
submission_page.draw()
|
|
|
|
text = '(Not enough space to display)'.encode('ascii')
|
|
window = terminal.stdscr.subwin
|
|
window.subwin.addstr.assert_any_call(6, 1, text, curses.A_NORMAL)
|
|
|
|
|
|
def test_submission_vote(submission_page, refresh_token):
|
|
|
|
# Log in
|
|
submission_page.config.refresh_token = refresh_token
|
|
submission_page.oauth.authorize()
|
|
|
|
# Test voting on the submission
|
|
with mock.patch('tuir.packages.praw.objects.Submission.upvote') as upvote, \
|
|
mock.patch('tuir.packages.praw.objects.Submission.downvote') as downvote, \
|
|
mock.patch('tuir.packages.praw.objects.Submission.clear_vote') as clear_vote:
|
|
|
|
data = submission_page.get_selected_item()
|
|
data['object'].archived = False
|
|
|
|
# Upvote
|
|
submission_page.controller.trigger('a')
|
|
assert upvote.called
|
|
assert data['likes'] is True
|
|
|
|
# Clear vote
|
|
submission_page.controller.trigger('a')
|
|
assert clear_vote.called
|
|
assert data['likes'] is None
|
|
|
|
# Upvote
|
|
submission_page.controller.trigger('a')
|
|
assert upvote.called
|
|
assert data['likes'] is True
|
|
|
|
# Downvote
|
|
submission_page.controller.trigger('z')
|
|
assert downvote.called
|
|
assert data['likes'] is False
|
|
|
|
# Clear vote
|
|
submission_page.controller.trigger('z')
|
|
assert clear_vote.called
|
|
assert data['likes'] is None
|
|
|
|
# Upvote - exception
|
|
upvote.side_effect = KeyboardInterrupt
|
|
submission_page.controller.trigger('a')
|
|
assert data['likes'] is None
|
|
|
|
# Downvote - exception
|
|
downvote.side_effect = KeyboardInterrupt
|
|
submission_page.controller.trigger('a')
|
|
assert data['likes'] is None
|
|
|
|
|
|
def test_submission_vote_archived(submission_page, refresh_token, terminal):
|
|
|
|
# Log in
|
|
submission_page.config.refresh_token = refresh_token
|
|
submission_page.oauth.authorize()
|
|
|
|
# Load an archived submission
|
|
archived_url = 'https://www.reddit.com/r/IAmA/comments/z1c9z/'
|
|
submission_page.refresh_content(name=archived_url)
|
|
|
|
with mock.patch.object(terminal, 'show_notification') as show_notification:
|
|
data = submission_page.get_selected_item()
|
|
|
|
# Upvote the submission
|
|
show_notification.reset_mock()
|
|
submission_page.controller.trigger('a')
|
|
show_notification.assert_called_with('Voting disabled for archived post', style='Error')
|
|
assert data['likes'] is None
|
|
|
|
# Downvote the submission
|
|
show_notification.reset_mock()
|
|
submission_page.controller.trigger('z')
|
|
show_notification.assert_called_with('Voting disabled for archived post', style='Error')
|
|
assert data['likes'] is None
|
|
|
|
|
|
def test_submission_save(submission_page, refresh_token):
|
|
|
|
# Log in
|
|
submission_page.config.refresh_token = refresh_token
|
|
submission_page.oauth.authorize()
|
|
|
|
# Test save on the submission
|
|
with mock.patch('tuir.packages.praw.objects.Submission.save') as save, \
|
|
mock.patch('tuir.packages.praw.objects.Submission.unsave') as unsave:
|
|
|
|
data = submission_page.content.get(submission_page.nav.absolute_index)
|
|
|
|
# Save
|
|
submission_page.controller.trigger('w')
|
|
assert save.called
|
|
assert data['saved'] is True
|
|
|
|
# Unsave
|
|
submission_page.controller.trigger('w')
|
|
assert unsave.called
|
|
assert data['saved'] is False
|
|
|
|
# Save - exception
|
|
save.side_effect = KeyboardInterrupt
|
|
submission_page.controller.trigger('w')
|
|
assert data['saved'] is False
|
|
|
|
|
|
def test_submission_comment_save(submission_page, terminal, refresh_token):
|
|
|
|
# Log in
|
|
submission_page.config.refresh_token = refresh_token
|
|
submission_page.oauth.authorize()
|
|
|
|
# Move down to the first comment
|
|
with mock.patch.object(submission_page, 'clear_input_queue'):
|
|
submission_page.controller.trigger('j')
|
|
|
|
# Test save on the comment submission
|
|
with mock.patch('tuir.packages.praw.objects.Comment.save') as save, \
|
|
mock.patch('tuir.packages.praw.objects.Comment.unsave') as unsave:
|
|
|
|
data = submission_page.content.get(submission_page.nav.absolute_index)
|
|
|
|
# Save
|
|
submission_page.controller.trigger('w')
|
|
assert save.called
|
|
assert data['saved'] is True
|
|
|
|
# Unsave
|
|
submission_page.controller.trigger('w')
|
|
assert unsave.called
|
|
assert data['saved'] is False
|
|
|
|
# Save - exception
|
|
save.side_effect = KeyboardInterrupt
|
|
submission_page.controller.trigger('w')
|
|
assert data['saved'] is False
|
|
|
|
|
|
def test_submission_comment(submission_page, terminal, refresh_token):
|
|
|
|
# Log in
|
|
submission_page.config.refresh_token = refresh_token
|
|
submission_page.oauth.authorize()
|
|
|
|
# Leave a comment
|
|
with mock.patch('tuir.packages.praw.objects.Submission.add_comment') as add_comment, \
|
|
mock.patch.object(terminal, 'open_editor') as open_editor, \
|
|
mock.patch('time.sleep'):
|
|
open_editor.return_value.__enter__.return_value = 'comment text'
|
|
submission_page.controller.trigger('c')
|
|
assert open_editor.called
|
|
add_comment.assert_called_with('comment text')
|
|
|
|
|
|
def test_submission_delete(submission_page, terminal, refresh_token):
|
|
|
|
# Log in
|
|
submission_page.config.refresh_token = refresh_token
|
|
submission_page.oauth.authorize()
|
|
|
|
# Can't delete the submission
|
|
curses.flash.reset_mock()
|
|
submission_page.controller.trigger('d')
|
|
assert curses.flash.called
|
|
|
|
# Move down to the first comment
|
|
with mock.patch.object(submission_page, 'clear_input_queue'):
|
|
submission_page.controller.trigger('j')
|
|
|
|
# Try to delete the first comment - wrong author
|
|
curses.flash.reset_mock()
|
|
submission_page.controller.trigger('d')
|
|
assert curses.flash.called
|
|
|
|
# Spoof the author and try to delete again
|
|
data = submission_page.content.get(submission_page.nav.absolute_index)
|
|
data['author'] = submission_page.reddit.user.name
|
|
with mock.patch('tuir.packages.praw.objects.Comment.delete') as delete, \
|
|
mock.patch.object(terminal.stdscr, 'getch') as getch, \
|
|
mock.patch('time.sleep'):
|
|
getch.return_value = ord('y')
|
|
submission_page.controller.trigger('d')
|
|
assert delete.called
|
|
|
|
|
|
def test_submission_edit(submission_page, terminal, refresh_token):
|
|
|
|
# Log in
|
|
submission_page.config.refresh_token = refresh_token
|
|
submission_page.oauth.authorize()
|
|
|
|
# Try to edit the submission - wrong author
|
|
data = submission_page.content.get(submission_page.nav.absolute_index)
|
|
data['author'] = 'some other person'
|
|
curses.flash.reset_mock()
|
|
submission_page.controller.trigger('e')
|
|
assert curses.flash.called
|
|
|
|
# Spoof the submission and try to edit again
|
|
data = submission_page.content.get(submission_page.nav.absolute_index)
|
|
data['author'] = submission_page.reddit.user.name
|
|
with mock.patch('tuir.packages.praw.objects.Submission.edit') as edit, \
|
|
mock.patch.object(terminal, 'open_editor') as open_editor, \
|
|
mock.patch('time.sleep'):
|
|
open_editor.return_value.__enter__.return_value = 'submission text'
|
|
|
|
submission_page.controller.trigger('e')
|
|
assert open_editor.called
|
|
edit.assert_called_with('submission text')
|
|
|
|
# Move down to the first comment
|
|
with mock.patch.object(submission_page, 'clear_input_queue'):
|
|
submission_page.controller.trigger('j')
|
|
|
|
# Spoof the author and edit the comment
|
|
data = submission_page.content.get(submission_page.nav.absolute_index)
|
|
data['author'] = submission_page.reddit.user.name
|
|
with mock.patch('tuir.packages.praw.objects.Comment.edit') as edit, \
|
|
mock.patch.object(terminal, 'open_editor') as open_editor, \
|
|
mock.patch('time.sleep'):
|
|
open_editor.return_value.__enter__.return_value = 'comment text'
|
|
|
|
submission_page.controller.trigger('e')
|
|
assert open_editor.called
|
|
edit.assert_called_with('comment text')
|
|
|
|
|
|
def test_submission_urlview(submission_page, terminal, refresh_token):
|
|
|
|
# Log in
|
|
submission_page.config.refresh_token = refresh_token
|
|
submission_page.oauth.authorize()
|
|
|
|
# Submission case
|
|
data = submission_page.content.get(submission_page.nav.absolute_index)
|
|
data['body'] = 'test comment body ❤'
|
|
with mock.patch.object(terminal, 'open_urlview') as open_urlview:
|
|
submission_page.controller.trigger('b')
|
|
open_urlview.assert_called_with('test comment body ❤')
|
|
|
|
# Subreddit case
|
|
data = submission_page.content.get(submission_page.nav.absolute_index)
|
|
data['text'] = ''
|
|
data['body'] = ''
|
|
data['url_full'] = 'http://test.url.com ❤'
|
|
with mock.patch.object(terminal, 'open_urlview') as open_urlview, \
|
|
mock.patch('subprocess.Popen'):
|
|
submission_page.controller.trigger('b')
|
|
open_urlview.assert_called_with('http://test.url.com ❤')
|
|
|
|
|
|
def test_submission_prompt_and_select_link(submission_page, terminal):
|
|
|
|
# A link submission should return the URL that it's pointing to
|
|
link = submission_page.prompt_and_select_link()
|
|
assert link == 'https://github.com/michael-lazar/rtv'
|
|
|
|
with mock.patch.object(submission_page, 'clear_input_queue'):
|
|
submission_page.controller.trigger('j')
|
|
|
|
# The first comment doesn't have any links in the comment body
|
|
link = submission_page.prompt_and_select_link()
|
|
data = submission_page.get_selected_item()
|
|
assert link == data['permalink']
|
|
|
|
with mock.patch.object(submission_page, 'clear_input_queue'):
|
|
submission_page.controller.trigger('j')
|
|
|
|
# The second comment has a link embedded in the comment body, and
|
|
# the user is prompted to select which link to open
|
|
with mock.patch.object(terminal, 'prompt_user_to_select_link') as prompt:
|
|
prompt.return_value = 'https://selected_link'
|
|
|
|
link = submission_page.prompt_and_select_link()
|
|
data = submission_page.get_selected_item()
|
|
|
|
assert link == prompt.return_value
|
|
|
|
embedded_url = 'http://peterdowns.com/posts/first-time-with-pypi.html'
|
|
assert prompt.call_args[0][0] == [
|
|
{'text': 'Permalink', 'href': data['permalink']},
|
|
{'text': 'Relevant tutorial', 'href': embedded_url}
|
|
]
|
|
|
|
submission_page.controller.trigger(' ')
|
|
|
|
# The comment is now hidden so there are no links to select
|
|
link = submission_page.prompt_and_select_link()
|
|
assert link is None
|