diff --git a/rtv/oauth.py b/rtv/oauth.py index 2b8a263..13c5945 100644 --- a/rtv/oauth.py +++ b/rtv/oauth.py @@ -193,23 +193,23 @@ class OAuthHelper(object): # If an exception is raised it will be seen by the thread # so we don't need to explicitly shutdown() the server _logger.exception(e) - self.term.show_notification('Browser Error') + self.term.show_notification('Browser Error', style='error') else: self.server.shutdown() finally: thread.join() if self.params['error'] == 'access_denied': - self.term.show_notification('Denied access') + self.term.show_notification('Denied access', style='error') return elif self.params['error']: - self.term.show_notification('Authentication error') + self.term.show_notification('Authentication error', style='error') return elif self.params['state'] is None: # Something went wrong but it's not clear what happened return elif self.params['state'] != state: - self.term.show_notification('UUID mismatch') + self.term.show_notification('UUID mismatch', style='error') return with self.term.loader('Logging in'): diff --git a/rtv/page.py b/rtv/page.py index 14e8c6b..29ec624 100644 --- a/rtv/page.py +++ b/rtv/page.py @@ -4,7 +4,6 @@ from __future__ import unicode_literals import os import sys import time -import curses import logging from functools import wraps @@ -60,7 +59,7 @@ class Page(object): def refresh_content(self, order=None, name=None): raise NotImplementedError - def _draw_item(self, window, data, inverted, highlight): + def _draw_item(self, window, data, inverted): raise NotImplementedError def get_selected_item(self): @@ -467,7 +466,8 @@ class Page(object): if self.content.order is not None: order = self.content.order.split('-')[0] col = text.find(order) - 3 - window.chgat(0, col, 3, self.term.attr('order_bar', True)) + attr = self.term.theme.get('order_bar', modifier='selected') + window.chgat(0, col, 3, attr) self._row += 1 @@ -536,12 +536,15 @@ class Page(object): # Now that the windows are setup, we can take a second pass through # to draw the content for index, (win, data, inverted) in enumerate(self._subwindows): - highlight = (index == self.nav.cursor_index) - if highlight: - win.bkgd(str(' '), self.term.attr('@highlight')) + if index == self.nav.cursor_index: + # This lets the theme know to invert the cursor + modifier = 'selected' else: - win.bkgd(str(' '), self.term.attr('@normal')) - self._draw_item(win, data, inverted, highlight) + modifier = None + + with self.term.theme.set_modifier(modifier): + win.bkgd(str(' '), self.term.attr('normal')) + self._draw_item(win, data, inverted) self._row += win_n_rows diff --git a/rtv/submission_page.py b/rtv/submission_page.py index f637596..240a6e8 100644 --- a/rtv/submission_page.py +++ b/rtv/submission_page.py @@ -3,7 +3,6 @@ from __future__ import unicode_literals import re import time -import curses from . import docs from .content import SubmissionContent, SubredditContent @@ -264,16 +263,18 @@ class SubmissionPage(Page): self.clear_input_queue() - def _draw_item(self, win, data, inverted, highlight): + def _draw_item(self, win, data, inverted): - if data['type'] in ('MoreComments', 'HiddenComment'): - self._draw_more_comments(win, data, highlight) + if data['type'] == 'MoreComments': + return self._draw_more_comments(win, data) + elif data['type'] == 'HiddenComment': + return self._draw_more_comments(win, data) elif data['type'] == 'Comment': - self._draw_comment(win, data, inverted, highlight) + return self._draw_comment(win, data, inverted) else: - self._draw_submission(win, data, highlight) + return self._draw_submission(win, data) - def _draw_comment(self, win, data, inverted, highlight): + def _draw_comment(self, win, data, inverted): n_rows, n_cols = win.getmaxyx() n_cols -= 1 @@ -287,7 +288,7 @@ class SubmissionPage(Page): split_body = data['split_body'] if data['n_rows'] > n_rows: # Only when there is a single comment on the page and not inverted - if not inverted and len(self._subwindows) == 0: + if not inverted and len(self._subwindows) == 1: cutoff = data['n_rows'] - n_rows + 1 split_body = split_body[:-cutoff] split_body.append('(Not enough space to display)') @@ -295,104 +296,104 @@ class SubmissionPage(Page): row = offset if row in valid_rows: if data['is_author']: - attr = self.term.attr('comment_author_self', highlight) + attr = self.term.attr('comment_author_self') text = '{author} [S]'.format(**data) else: - attr = self.term.attr('comment_author', highlight) + attr = self.term.attr('comment_author') text = '{author}'.format(**data) self.term.add_line(win, text, row, 1, attr) if data['flair']: - attr = self.term.attr('user_flair', highlight) + attr = self.term.attr('user_flair') self.term.add_space(win) self.term.add_line(win, '{flair}'.format(**data), attr=attr) - arrow, attr = self.term.get_arrow(data['likes'], highlight) + arrow, attr = self.term.get_arrow(data['likes']) self.term.add_space(win) self.term.add_line(win, arrow, attr=attr) - attr = self.term.attr('score', highlight) + attr = self.term.attr('score') self.term.add_space(win) self.term.add_line(win, '{score}'.format(**data), attr=attr) - attr = self.term.attr('created', highlight) + attr = self.term.attr('created') self.term.add_space(win) self.term.add_line(win, '{created}'.format(**data), attr=attr) if data['gold']: - attr = self.term.attr('gold', highlight) + attr = self.term.attr('gold') self.term.add_space(win) self.term.add_line(win, self.term.guilded, attr=attr) if data['stickied']: - attr = self.term.attr('stickied', highlight) + attr = self.term.attr('stickied') self.term.add_space(win) self.term.add_line(win, '[stickied]', attr=attr) if data['saved']: - attr = self.term.attr('saved', highlight) + attr = self.term.attr('saved') self.term.add_space(win) self.term.add_line(win, '[saved]', attr=attr) for row, text in enumerate(split_body, start=offset+1): - attr = self.term.attr('comment_text', highlight) + attr = self.term.attr('comment_text') if row in valid_rows: self.term.add_line(win, text, row, 1, attr=attr) # Unfortunately vline() doesn't support custom color so we have to # build it one segment at a time. index = data['level'] % len(self.term.theme.BAR_LEVELS) - attr = self.term.attr(self.term.theme.BAR_LEVELS[index], highlight) + attr = self.term.attr(self.term.theme.BAR_LEVELS[index]) for y in range(n_rows): self.term.addch(win, y, 0, self.term.vline, attr) - def _draw_more_comments(self, win, data, highlight): + def _draw_more_comments(self, win, data): n_rows, n_cols = win.getmaxyx() n_cols -= 1 - attr = self.term.attr('hidden_comment_text', highlight) + attr = self.term.attr('hidden_comment_text') self.term.add_line(win, '{body}'.format(**data), 0, 1, attr=attr) - attr = self.term.attr('hidden_comment_expand', highlight) + attr = self.term.attr('hidden_comment_expand') self.term.add_space(win) self.term.add_line(win, '[{count}]'.format(**data), attr=attr) index = data['level'] % len(self.term.theme.BAR_LEVELS) - attr = self.term.attr(self.term.theme.BAR_LEVELS[index], highlight) + attr = self.term.attr(self.term.theme.BAR_LEVELS[index]) self.term.addch(win, 0, 0, self.term.vline, attr) - def _draw_submission(self, win, data, highlight): + def _draw_submission(self, win, data): n_rows, n_cols = win.getmaxyx() n_cols -= 3 # one for each side of the border + one for offset - attr = self.term.attr('submission_title', highlight) + attr = self.term.attr('submission_title') for row, text in enumerate(data['split_title'], start=1): self.term.add_line(win, text, row, 1, attr) row = len(data['split_title']) + 1 - attr = self.term.attr('submission_author', highlight) + attr = self.term.attr('submission_author') self.term.add_line(win, '{author}'.format(**data), row, 1, attr) if data['flair']: - attr = self.term.attr('submission_flair', highlight) + attr = self.term.attr('submission_flair') self.term.add_space(win) self.term.add_line(win, '{flair}'.format(**data), attr=attr) - attr = self.term.attr('created', highlight) + attr = self.term.attr('created') self.term.add_space(win) self.term.add_line(win, '{created}'.format(**data), attr=attr) - attr = self.term.attr('submission_subreddit', highlight) + attr = self.term.attr('submission_subreddit') self.term.add_space(win) self.term.add_line(win, '/r/{subreddit}'.format(**data), attr=attr) row = len(data['split_title']) + 2 if data['url_full'] in self.config.history: - attr = self.term.attr('url_seen', highlight) + attr = self.term.attr('url_seen') else: - attr = self.term.attr('url', highlight) + attr = self.term.attr('url') self.term.add_line(win, '{url}'.format(**data), row, 1, attr) offset = len(data['split_title']) + 3 @@ -404,34 +405,34 @@ class SubmissionPage(Page): split_text = split_text[:-cutoff] split_text.append('(Not enough space to display)') - attr = self.term.attr('submission_text', highlight) + attr = self.term.attr('submission_text') for row, text in enumerate(split_text, start=offset): self.term.add_line(win, text, row, 1, attr=attr) row = len(data['split_title']) + len(split_text) + 3 - attr = self.term.attr('score', highlight) + attr = self.term.attr('score') self.term.add_line(win, '{score}'.format(**data), row, 1, attr=attr) - arrow, attr = self.term.get_arrow(data['likes'], highlight) + arrow, attr = self.term.get_arrow(data['likes']) self.term.add_space(win) self.term.add_line(win, arrow, attr=attr) - attr = self.term.attr('comment_count', highlight) + attr = self.term.attr('comment_count') self.term.add_space(win) self.term.add_line(win, '{comments}'.format(**data), attr=attr) if data['gold']: - attr = self.term.attr('gold', highlight) + attr = self.term.attr('gold') self.term.add_space(win) self.term.add_line(win, self.term.guilded, attr=attr) if data['nsfw']: - attr = self.term.attr('nsfw', highlight) + attr = self.term.attr('nsfw') self.term.add_space(win) self.term.add_line(win, 'NSFW', attr=attr) if data['saved']: - attr = self.term.attr('saved', highlight) + attr = self.term.attr('saved') self.term.add_space(win) self.term.add_line(win, '[saved]', attr=attr) diff --git a/rtv/subreddit_page.py b/rtv/subreddit_page.py index 0e36dd1..1494d36 100644 --- a/rtv/subreddit_page.py +++ b/rtv/subreddit_page.py @@ -253,7 +253,7 @@ class SubredditPage(Page): self.content = page.selected_subreddit self.nav = Navigator(self.content.get) - def _draw_item(self, win, data, inverted, highlight): + def _draw_item(self, win, data, inverted): n_rows, n_cols = win.getmaxyx() n_cols -= 1 # Leave space for the cursor in the first column @@ -264,75 +264,75 @@ class SubredditPage(Page): n_title = len(data['split_title']) for row, text in enumerate(data['split_title'], start=offset): - attr = self.term.attr('submission_title', highlight) + attr = self.term.attr('submission_title') if row in valid_rows: self.term.add_line(win, text, row, 1, attr) row = n_title + offset if row in valid_rows: if data['url_full'] in self.config.history: - attr = self.term.attr('url_seen', highlight) + attr = self.term.attr('url_seen') else: - attr = self.term.attr('url', highlight) + attr = self.term.attr('url') self.term.add_line(win, '{url}'.format(**data), row, 1, attr) row = n_title + offset + 1 if row in valid_rows: - attr = self.term.attr('score', highlight) + attr = self.term.attr('score') self.term.add_line(win, '{score}'.format(**data), row, 1, attr) self.term.add_space(win) - arrow, attr = self.term.get_arrow(data['likes'], highlight) + arrow, attr = self.term.get_arrow(data['likes']) self.term.add_line(win, arrow, attr=attr) self.term.add_space(win) - attr = self.term.attr('created', highlight) + attr = self.term.attr('created') self.term.add_line(win, '{created}'.format(**data), attr=attr) if data['comments'] is not None: - attr = self.term.attr('separator', highlight) + attr = self.term.attr('separator') self.term.add_space(win) self.term.add_line(win, '-', attr=attr) - attr = self.term.attr('comment_count', highlight) + attr = self.term.attr('comment_count') self.term.add_space(win) self.term.add_line(win, '{comments}'.format(**data), attr=attr) if data['saved']: - attr = self.term.attr('saved', highlight) + attr = self.term.attr('saved') self.term.add_space(win) self.term.add_line(win, '[saved]', attr=attr) if data['stickied']: - attr = self.term.attr('stickied', highlight) + attr = self.term.attr('stickied') self.term.add_space(win) self.term.add_line(win, '[stickied]', attr=attr) if data['gold']: - attr = self.term.attr('gold', highlight) + attr = self.term.attr('gold') self.term.add_space(win) self.term.add_line(win, self.term.guilded, attr=attr) if data['nsfw']: - attr = self.term.attr('nsfw', highlight) + attr = self.term.attr('nsfw') self.term.add_space(win) self.term.add_line(win, 'NSFW', attr=attr) row = n_title + offset + 2 if row in valid_rows: - attr = self.term.attr('submission_author', highlight) + attr = self.term.attr('submission_author') self.term.add_line(win, '{author}'.format(**data), row, 1, attr) self.term.add_space(win) - attr = self.term.attr('submission_subreddit', highlight) + attr = self.term.attr('submission_subreddit') self.term.add_line(win, '/r/{subreddit}'.format(**data), attr=attr) if data['flair']: - attr = self.term.attr('submission_flair', highlight) + attr = self.term.attr('submission_flair') self.term.add_space(win) self.term.add_line(win, '{flair}'.format(**data), attr=attr) - attr = self.term.attr('cursor', highlight) + attr = self.term.attr('cursor') for y in range(n_rows): self.term.addch(win, y, 0, str(' '), attr) diff --git a/rtv/subscription_page.py b/rtv/subscription_page.py index 402f350..55cca19 100644 --- a/rtv/subscription_page.py +++ b/rtv/subscription_page.py @@ -83,7 +83,7 @@ class SubscriptionPage(Page): # Subscriptions can't be sorted, so disable showing the order menu pass - def _draw_item(self, win, data, inverted, highlight): + def _draw_item(self, win, data, inverted): n_rows, n_cols = win.getmaxyx() n_cols -= 1 # Leave space for the cursor in the first column @@ -94,20 +94,20 @@ class SubscriptionPage(Page): row = offset if row in valid_rows: if data['type'] == 'Multireddit': - attr = self.term.attr('multireddit_name', highlight) + attr = self.term.attr('multireddit_name') else: - attr = self.term.attr('subscription_name', highlight) + attr = self.term.attr('subscription_name') self.term.add_line(win, '{name}'.format(**data), row, 1, attr) row = offset + 1 for row, text in enumerate(data['split_title'], start=row): if row in valid_rows: if data['type'] == 'Multireddit': - attr = self.term.attr('multireddit_text', highlight) + attr = self.term.attr('multireddit_text') else: - attr = self.term.attr('subscription_text', highlight) + attr = self.term.attr('subscription_text') self.term.add_line(win, text, row, 1, attr) - attr = self.term.attr('cursor', highlight) + attr = self.term.attr('cursor') for y in range(n_rows): self.term.addch(win, y, 0, str(' '), attr) diff --git a/rtv/terminal.py b/rtv/terminal.py index 55cd11f..b07b646 100644 --- a/rtv/terminal.py +++ b/rtv/terminal.py @@ -183,7 +183,7 @@ class Terminal(object): finally: self.stdscr.nodelay(0) - def get_arrow(self, likes, highlight=False): + def get_arrow(self, likes): """ Curses does define constants for symbols (e.g. curses.ACS_BULLET). However, they rely on using the curses.addch() function, which has been @@ -193,11 +193,11 @@ class Terminal(object): """ if likes is None: - return self.neutral_arrow, self.attr('neutral_vote', highlight) + return self.neutral_arrow, self.attr('neutral_vote') elif likes: - return self.up_arrow, self.attr('upvote', highlight) + return self.up_arrow, self.attr('upvote') else: - return self.down_arrow, self.attr('downvote', highlight) + return self.down_arrow, self.attr('downvote') def clean(self, string, n_cols=None): """ @@ -282,7 +282,8 @@ class Terminal(object): row, col = window.getyx() _, max_cols = window.getmaxyx() - if max_cols - col - 1 <= 0: + n_cols = max_cols - col - 1 + if n_cols <= 0: # Trying to draw outside of the screen bounds return @@ -300,6 +301,8 @@ class Terminal(object): notification window """ + assert style in ('info', 'warning', 'error', 'success') + if isinstance(message, six.string_types): message = message.splitlines() @@ -396,7 +399,7 @@ class Terminal(object): _logger.warning(stderr) self.show_notification( 'Program exited with status={0}\n{1}'.format( - code, stderr.strip())) + code, stderr.strip()), style='error') else: # Non-blocking, open a background process @@ -821,17 +824,18 @@ class Terminal(object): else: self.stdscr.clearok(True) - def attr(self, element, highlight=False): + def attr(self, element): """ Shortcut for fetching the color + attribute code for an element. """ - return self.theme.get(element, highlight=highlight) + + return self.theme.get(element) def set_theme(self, theme=None): """ Check that the terminal supports the provided theme, and applies the theme to the terminal if possible. - + If the terminal doesn't support the theme, this falls back to the default theme. The default theme only requires 8 colors so it should be compatible with any terminal that supports basic colors. @@ -862,4 +866,4 @@ class Terminal(object): # Apply the default color to the whole screen self.stdscr.bkgd(str(' '), theme.get('@normal')) - self.theme = theme + self.theme = theme \ No newline at end of file diff --git a/rtv/theme.py b/rtv/theme.py index 187d63c..0f74703 100644 --- a/rtv/theme.py +++ b/rtv/theme.py @@ -1,8 +1,9 @@ +import os import codecs -import configparser import curses import logging -import os +import configparser +from contextlib import contextmanager from .config import THEMES, DEFAULT_THEMES from .exceptions import ConfigError @@ -116,6 +117,7 @@ class Theme(object): self.monochrome = monochrome self._color_pair_map = None self._attribute_map = None + self._modifier = None self.required_color_pairs = 0 self.required_colors = 0 @@ -206,7 +208,7 @@ class Theme(object): self._attribute_map[element] = attrs - def get(self, val, highlight=False): + def get(self, element, modifier=None): """ Returns the curses attribute code for the given element. """ @@ -214,10 +216,25 @@ class Theme(object): raise RuntimeError('Attempted to access theme attribute before ' 'calling initialize_curses_theme()') - if highlight: - val = val + '.highlight' + modifier = modifier or self._modifier + if modifier: + modified_element = '{0}.{1}'.format(element, modifier) + if modified_element in self._elements: + return self._elements[modified_element] - return self._attribute_map[val] + return self._attribute_map[element] + + @contextmanager + def set_modifier(self, modifier=None): + + # This case is undefined if the context manager is nested + assert self._modifier is None + + self._modifier = modifier + try: + yield + finally: + self._modifier = None @classmethod def list_themes(cls, path=THEMES): diff --git a/tests/conftest.py b/tests/conftest.py index c14864e..a42a4ef 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -52,6 +52,9 @@ class MockStdscr(mock.MagicMock): def getyx(self): return self.y, self.x + def getbegyx(self): + return 0, 0 + def getmaxyx(self): return self.nlines, self.ncols @@ -154,12 +157,14 @@ def stdscr(): patch('curses.curs_set'), \ patch('curses.init_pair'), \ patch('curses.color_pair'), \ + patch('curses.has_colors'), \ patch('curses.start_color'), \ patch('curses.use_default_colors'): out = MockStdscr(nlines=40, ncols=80, x=0, y=0) curses.initscr.return_value = out curses.newwin.side_effect = lambda *args: out.derwin(*args) curses.color_pair.return_value = 23 + curses.has_colors.return_value = True curses.ACS_VLINE = 0 yield out diff --git a/tests/test_objects.py b/tests/test_objects.py index 411fd7c..dde5c3f 100644 --- a/tests/test_objects.py +++ b/tests/test_objects.py @@ -12,7 +12,7 @@ import requests from six.moves import reload_module from rtv import exceptions -from rtv.objects import Color, Controller, Navigator, Command, KeyMap, \ +from rtv.objects import Controller, Navigator, Command, KeyMap, \ curses_session, patch_webbrowser try: @@ -189,21 +189,6 @@ def test_objects_load_screen_nested_complex(terminal, stdscr, use_ascii): 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() - - # 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 diff --git a/tests/test_submission.py b/tests/test_submission.py index 698c726..d796f1b 100644 --- a/tests/test_submission.py +++ b/tests/test_submission.py @@ -79,15 +79,16 @@ def test_submission_page_construct(reddit, terminal, config, oauth): # 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) + 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) + 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 window.subwin.chgat.called + 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 @@ -264,7 +265,7 @@ def test_submission_comment_not_enough_space(submission_page, terminal): text = '(Not enough space to display)'.encode('ascii') window = terminal.stdscr.subwin - window.subwin.addstr.assert_any_call(6, 1, text) + window.subwin.addstr.assert_any_call(6, 1, text, curses.A_NORMAL) def test_submission_vote(submission_page, refresh_token): diff --git a/tests/test_subreddit.py b/tests/test_subreddit.py index 09d1a1a..b5fba6f 100644 --- a/tests/test_subreddit.py +++ b/tests/test_subreddit.py @@ -1,6 +1,8 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals +import curses + import six from rtv import __version__ @@ -38,7 +40,7 @@ def test_subreddit_page_construct(reddit, terminal, config, oauth): window.subwin.addstr.assert_any_call(0, 1, text, 2097152) # Cursor should have been drawn - assert window.subwin.chgat.called + window.subwin.addch.assert_any_call(0, 0, ' ', curses.A_REVERSE) # Reload with a smaller terminal window terminal.stdscr.ncols = 20 diff --git a/tests/test_subscription.py b/tests/test_subscription.py index 1cd635c..239a291 100644 --- a/tests/test_subscription.py +++ b/tests/test_subscription.py @@ -45,8 +45,8 @@ def test_subscription_page_construct(reddit, terminal, config, oauth, window.addstr.assert_any_call(0, 0, menu) # Cursor - 2 lines - window.subwin.chgat.assert_any_call(0, 0, 1, 262144) - window.subwin.chgat.assert_any_call(1, 0, 1, 262144) + window.subwin.addch.assert_any_call(0, 0, ' ', 262144) + window.subwin.addch.assert_any_call(1, 0, ' ', 262144) # Reload with a smaller terminal window terminal.stdscr.ncols = 20 diff --git a/tests/test_terminal.py b/tests/test_terminal.py index 15fd42e..a548ae0 100644 --- a/tests/test_terminal.py +++ b/tests/test_terminal.py @@ -9,6 +9,7 @@ import codecs import six import pytest +from rtv.theme import Theme from rtv.docs import HELP, COMMENT_EDIT_FILE from rtv.exceptions import TemporaryFileError, BrowserError @@ -20,14 +21,10 @@ except ImportError: def test_terminal_properties(terminal, config): - assert len(terminal.up_arrow) == 2 - assert isinstance(terminal.up_arrow[0], six.text_type) - assert len(terminal.down_arrow) == 2 - assert isinstance(terminal.down_arrow[0], six.text_type) - assert len(terminal.neutral_arrow) == 2 - assert isinstance(terminal.neutral_arrow[0], six.text_type) - assert len(terminal.guilded) == 2 - assert isinstance(terminal.guilded[0], six.text_type) + assert isinstance(terminal.up_arrow, six.text_type) + assert isinstance(terminal.down_arrow, six.text_type) + assert isinstance(terminal.neutral_arrow, six.text_type) + assert isinstance(terminal.guilded, six.text_type) terminal._display = None with mock.patch('rtv.terminal.sys') as sys, \ @@ -60,6 +57,8 @@ def test_terminal_properties(terminal, config): assert terminal.MIN_HEIGHT is not None assert terminal.MIN_WIDTH is not None + assert terminal.theme is not None + def test_terminal_functions(terminal): @@ -558,3 +557,42 @@ def test_strip_textpad(terminal): text = 'alpha bravo\ncharlie \ndelta \n echo \n\nfoxtrot\n\n\n' assert terminal.strip_textpad(text) == ( 'alpha bravocharlie delta\n echo\n\nfoxtrot') + + +def test_add_space(terminal, stdscr): + + stdscr.x, stdscr.y = 10, 20 + terminal.add_space(stdscr) + stdscr.addstr.assert_called_with(20, 10, ' ') + + # Not enough room to add a space + stdscr.reset_mock() + stdscr.x = 10 + stdscr.ncols = 11 + terminal.add_space(stdscr) + assert not stdscr.addstr.called + + +def test_attr(terminal): + + assert terminal.attr('cursor') == 0 + assert terminal.attr('cursor.selected') == curses.A_REVERSE + assert terminal.attr('neutral_vote') == curses.A_BOLD + + with terminal.theme.set_modifier('selected'): + assert terminal.attr('cursor') == curses.A_REVERSE + assert terminal.attr('neutral_vote') == curses.A_BOLD + + +def test_set_theme(terminal, stdscr): + + stdscr.reset_mock() + terminal.set_theme() + assert not terminal.theme.monochrome + stdscr.bkgd.assert_called_once_with(' ', 0) + + stdscr.reset_mock() + theme = Theme(monochrome=True) + terminal.set_theme(theme=theme) + assert terminal.theme.monochrome + stdscr.bkgd.assert_called_once_with(' ', 0)