commit cfaf6953c5a2df4521fb48e464b8415b594d4a11 Author: Damian Johnson atagar@torproject.org Date: Mon Apr 4 15:36:14 2016 -0700
Revise descriptor dialog
Definitely the largest, thorniest in this file. Rejiggered code, migrating to the new draw() function and adding tests. --- nyx/curses.py | 69 ++++++++++++++++++-- nyx/panel/connection.py | 8 +-- nyx/panel/graph.py | 4 +- nyx/panel/log.py | 2 +- nyx/popups.py | 170 ++++++++++++++++++++---------------------------- test/popups.py | 119 +++++++++++++++++++++++++-------- 6 files changed, 232 insertions(+), 140 deletions(-)
diff --git a/nyx/curses.py b/nyx/curses.py index 6d3b170..3718742 100644 --- a/nyx/curses.py +++ b/nyx/curses.py @@ -15,6 +15,7 @@ if we want Windows support in the future too. raw_screen - provides direct access to the curses screen key_input - get keypress by user curses_attr - curses encoded text attribute + screen_size - provides the dimensions of our screen screenshot - dump of the present on-screen content
is_color_supported - checks if terminal supports color output @@ -76,11 +77,13 @@ if we want Windows support in the future too.
from __future__ import absolute_import
+import collections import curses import threading
import stem.util.conf import stem.util.enum +import stem.util.str_tools import stem.util.system
from nyx import msg, log @@ -135,16 +138,21 @@ SPECIAL_KEYS = { 'esc': 27, }
+Dimensions = collections.namedtuple('Dimensions', ['width', 'height']) +
def conf_handler(key, value): if key == 'features.colorOverride': if value not in Color and value != 'None': raise ValueError(msg('usage.unable_to_set_color_override', color = value)) + elif key == 'features.torrc.maxLineWrap': + return max(1, value)
CONFIG = stem.util.conf.config_dict('nyx', { 'features.colorOverride': 'None', 'features.colorInterface': True, + 'features.maxLineWrap': 8, }, conf_handler)
@@ -251,6 +259,17 @@ def curses_attr(*attributes): return encoded
+def screen_size(): + """ + Provides the current dimensions of our screen. + + :returns: :data:`~nyx.curses.Dimensions` with our screen size + """ + + height, width = CURSES_SCREEN.getmaxyx() + return Dimensions(width, height) + + def screenshot(): """ Provides a dump of the present content of the screen. @@ -260,7 +279,7 @@ def screenshot():
lines = []
- for y in range(CURSES_SCREEN.getmaxyx()[0]): + for y in range(screen_size().height): lines.append(CURSES_SCREEN.instr(y, 0).rstrip())
return '\n'.join(lines).rstrip() @@ -408,10 +427,9 @@ def draw(func, left = 0, top = 0, width = None, height = None): """
with CURSES_LOCK: - max_height, max_width = CURSES_SCREEN.getmaxyx() - - subwindow_width = max(0, max_width - left) - subwindow_height = max(0, max_height - top) + dimensions = screen_size() + subwindow_width = max(0, dimensions.width - left) + subwindow_height = max(0, dimensions.height - top)
if width: subwindow_width = min(width, subwindow_width) @@ -443,7 +461,7 @@ class _Subwindow(object): Draws a string in the subwindow.
:param int x: horizontal location - :param int y, vertical location + :param int y: vertical location :param str msg: string to be written :param list attr: text attributes to apply """ @@ -458,6 +476,36 @@ class _Subwindow(object):
return x
+ def addstr_wrap(self, x, y, msg, width, min_x = 0, *attr): + """ + Draws a string in the subwindow, with text wrapped if it exceeds a width. + + :param int x: horizontal location + :param int y: vertical location + :param str msg: string to be written + :param int width: width avaialble to render the string + :param int min_x: horizontal position to wrap to on new lines + :param list attr: text attributes to apply + """ + + orig_y = y + + while msg: + draw_msg, msg = stem.util.str_tools.crop(msg, width - x, None, ending = None, get_remainder = True) + + if not draw_msg: + draw_msg, msg = stem.util.str_tools.crop(msg, width - x), '' # first word is longer than the line + + x = self.addstr(x, y, draw_msg, *attr) + + if (y - orig_y + 1) >= CONFIG['features.maxLineWrap']: + break # maximum number we'll wrap + + if msg: + x, y = min_x, y + 1 + + return x, y + def box(self, left = 0, top = 0, width = None, height = None, *attr): """ Draws a box with the given bounds. @@ -551,6 +599,15 @@ class KeyInput(object):
return self._key in (curses.KEY_ENTER, 10, ord(' '))
+ def __eq__(self, other): + if isinstance(other, KeyInput): + return self._key == other._key + else: + return False + + def __ne__(self, other): + return not self == other +
class Scroller(object): """ diff --git a/nyx/panel/connection.py b/nyx/panel/connection.py index e8eb0b5..afd1da1 100644 --- a/nyx/panel/connection.py +++ b/nyx/panel/connection.py @@ -376,14 +376,14 @@ class ConnectionPanel(nyx.panel.Panel, threading.Thread): return key.is_selection() or key.match('d') or key.match('left') or key.match('right')
color = CONFIG['attr.connection.category_color'].get(selected.entry.get_type(), WHITE) - key = nyx.popups.show_descriptor_popup(selected.fingerprint, color, self.max_x, is_close_key) + key = nyx.popups.show_descriptor(selected.fingerprint, color, is_close_key)
if not key or key.is_selection() or key.match('d'): break # closes popup elif key.match('left'): - self.handle_key(nyx.curses.KeyInput(curses.KEY_UP)) + _scroll(nyx.curses.KeyInput(curses.KEY_UP)) elif key.match('right'): - self.handle_key(nyx.curses.KeyInput(curses.KEY_DOWN)) + _scroll(nyx.curses.KeyInput(curses.KEY_DOWN))
self.redraw(True)
@@ -392,7 +392,7 @@ class ConnectionPanel(nyx.panel.Panel, threading.Thread): resolver = connection_tracker.get_custom_resolver() options = ['auto'] + list(connection.Resolver) + list(nyx.tracker.CustomResolver)
- selected = nyx.popups.show_selector('Connection Resolver:', options, resolver if resolver else 'auto') + selected = nyx.popups.show_list_selector('Connection Resolver:', options, resolver if resolver else 'auto') connection_tracker.set_custom_resolver(None if selected == 'auto' else selected)
self.redraw(True) diff --git a/nyx/panel/graph.py b/nyx/panel/graph.py index 2b40390..cad754b 100644 --- a/nyx/panel/graph.py +++ b/nyx/panel/graph.py @@ -510,7 +510,7 @@ class GraphPanel(nyx.panel.Panel): options = ['None'] + [stat.capitalize() for stat in available_stats] previous_selection = options[available_stats.index(self.displayed_stat) + 1] if self.displayed_stat else 'None'
- selection = nyx.popups.show_selector('Graphed Stats:', options, previous_selection) + selection = nyx.popups.show_list_selector('Graphed Stats:', options, previous_selection) self.displayed_stat = None if selection == 'None' else available_stats[options.index(selection) - 1]
def _next_bounds(): @@ -518,7 +518,7 @@ class GraphPanel(nyx.panel.Panel): self.redraw(True)
def _pick_interval(): - self.update_interval = nyx.popups.show_selector('Update Interval:', list(Interval), self.update_interval) + self.update_interval = nyx.popups.show_list_selector('Update Interval:', list(Interval), self.update_interval) self.redraw(True)
return ( diff --git a/nyx/panel/log.py b/nyx/panel/log.py index 765e251..4582885 100644 --- a/nyx/panel/log.py +++ b/nyx/panel/log.py @@ -237,7 +237,7 @@ class LogPanel(nyx.panel.Panel, threading.Thread): with nyx.curses.CURSES_LOCK: options = ['None'] + self._filter.latest_selections() + ['New...'] initial_selection = self._filter.selection() if self._filter.selection() else 'None' - selection = nyx.popups.show_selector('Log Filter:', options, initial_selection) + selection = nyx.popups.show_list_selector('Log Filter:', options, initial_selection)
if selection == 'None': self._filter.select(None) diff --git a/nyx/popups.py b/nyx/popups.py index 261cdce..4bf14fe 100644 --- a/nyx/popups.py +++ b/nyx/popups.py @@ -122,7 +122,7 @@ def show_help(): subwindow.addstr(2, 7, 'Press any key...')
with nyx.curses.CURSES_LOCK: - nyx.curses.draw(_render, top = control.header_panel().get_height(), width = 80, height = 9) + nyx.curses.draw(_render, top = _top(), width = 80, height = 9) keypress = nyx.curses.key_input()
if keypress.is_selection() or keypress.is_scroll() or keypress.match('left', 'right'): @@ -146,7 +146,7 @@ def show_about(): subwindow.addstr(2, 7, 'Press any key...')
with nyx.curses.CURSES_LOCK: - nyx.curses.draw(_render, top = nyx.controller.get_controller().header_panel().get_height(), width = 80, height = 9) + nyx.curses.draw(_render, top = _top(), width = 80, height = 9) nyx.curses.key_input()
@@ -188,18 +188,16 @@ def show_counts(title, counts, fill_char = ' '):
subwindow.addstr(2, subwindow.height - 2, 'Press any key...')
- top = nyx.controller.get_controller().header_panel().get_height() - with nyx.curses.CURSES_LOCK: if not counts: - nyx.curses.draw(_render_no_stats, top = top, width = len(NO_STATS_MSG) + 4, height = 3) + nyx.curses.draw(_render_no_stats, top = _top(), width = len(NO_STATS_MSG) + 4, height = 3) else: - nyx.curses.draw(_render_stats, top = top, width = 80, height = 4 + max(1, len(counts))) + nyx.curses.draw(_render_stats, top = _top(), width = 80, height = 4 + max(1, len(counts)))
nyx.curses.key_input()
-def show_selector(title, options, previous_selection): +def show_list_selector(title, options, previous_selection): """ Provides list of items the user can choose from.
@@ -211,7 +209,6 @@ def show_selector(title, options, previous_selection): """
selected_index = options.index(previous_selection) if previous_selection in options else 0 - top = nyx.controller.get_controller().header_panel().get_height()
def _render(subwindow): subwindow.box() @@ -226,8 +223,8 @@ def show_selector(title, options, previous_selection):
with nyx.curses.CURSES_LOCK: while True: - nyx.curses.draw(lambda subwindow: subwindow.addstr(0, 0, ' ' * 500), top = top, height = 1) # hides title below us - nyx.curses.draw(_render, top = top, width = max(map(len, options)) + 9, height = len(options) + 2) + nyx.curses.draw(lambda subwindow: subwindow.addstr(0, 0, ' ' * 500), top = _top(), height = 1) # hides title below us + nyx.curses.draw(_render, top = _top(), width = max(map(len, options)) + 9, height = len(options) + 2) key = nyx.curses.key_input()
if key.match('up'): @@ -285,7 +282,7 @@ def show_sort_dialog(title, options, previous_order, option_colors):
with nyx.curses.CURSES_LOCK: while len(new_order) < len(previous_order): - nyx.curses.draw(_render, top = nyx.controller.get_controller().header_panel().get_height(), width = 80, height = 9) + nyx.curses.draw(_render, top = _top(), width = 80, height = 9) key = nyx.curses.key_input()
if key.match('left'): @@ -311,13 +308,12 @@ def show_sort_dialog(title, options, previous_order, option_colors): return new_order
-def show_descriptor_popup(fingerprint, color, max_width, is_close_key): +def show_descriptor(fingerprint, color, is_close_key): """ - Provides a dialog showing the descriptors for a given relay. + Provides a dialog showing descriptors for a relay.
:param str fingerprint: fingerprint of the relay to be shown :param str color: text color of the dialog - :param int max_width: maximum width of the dialog :param function is_close_key: method to indicate if a key should close the dialog or not
@@ -326,39 +322,78 @@ def show_descriptor_popup(fingerprint, color, max_width, is_close_key): """
if fingerprint: - title = 'Consensus Descriptor:' - lines = _display_text(fingerprint) + title = 'Consensus Descriptor (%s):' % fingerprint + lines = _descriptor_text(fingerprint) show_line_numbers = True else: - title = 'Consensus Descriptor (%s):' % fingerprint + title = 'Consensus Descriptor:' lines = [UNRESOLVED_MSG] show_line_numbers = False
- popup_height, popup_width = _preferred_size(lines, max_width, show_line_numbers) + scroller = nyx.curses.Scroller() + line_number_width = int(math.log10(len(lines))) + 1 if show_line_numbers else 0
- with popup_window(popup_height, popup_width) as (popup, _, height): - if not popup: - return None + def _render(subwindow): + in_block = False # flag indicating if we're currently in crypto content + y, offset = 1, line_number_width + 3 if show_line_numbers else 2 + + for i, line in enumerate(lines): + keyword, value = line, '' + line_color = color + + if line in HEADERS: + line_color = HEADER_COLOR + elif line.startswith(BLOCK_START): + in_block = True + elif line.startswith(BLOCK_END): + in_block = False + elif in_block: + keyword, value = '', line + elif ' ' in line and line != UNRESOLVED_MSG and line != ERROR_MSG: + keyword, value = line.split(' ', 1) + keyword = keyword + ' ' + + if i < scroller.location(): + continue + + if show_line_numbers: + subwindow.addstr(2, y, str(i + 1).rjust(line_number_width), LINE_NUMBER_COLOR, BOLD) + + x, y = subwindow.addstr_wrap(3 + line_number_width, y, keyword, subwindow.width - 2, offset, line_color, BOLD) + x, y = subwindow.addstr_wrap(x, y, value, subwindow.width - 2, offset, line_color) + y += 1 + + if y > subwindow.height - 2: + break
- with popup_window(1, -1) as (title_erase, _, _): - title_erase.addstr(0, 0, ' ' * 500) # hide title of the panel below us + subwindow.box() + subwindow.addstr(0, 0, title, HIGHLIGHT)
- scroller, redraw = nyx.curses.Scroller(), True + width, height = 0, len(lines) + 2 + screen_size = nyx.curses.screen_size()
- while True: - if redraw: - _draw(popup, title, lines, color, scroller.location(), show_line_numbers) - redraw = False + for line in lines: + width = min(screen_size.width, max(width, len(line) + line_number_width + 5)) + height += len(line) / (screen_size.width - line_number_width - 5) # extra lines due to text wrap
- key = nyx.curses.key_input() + with nyx.curses.CURSES_LOCK: + nyx.curses.draw(lambda subwindow: subwindow.addstr(0, 0, ' ' * 500), top = _top(), height = 1) # hides title below us + nyx.curses.draw(_render, top = _top(), width = width, height = height) + popup_height = min(screen_size.height - _top(), height) + + while True: + key = nyx.curses.key_input()
- if key.is_scroll(): - redraw = scroller.handle_key(key, len(lines), height - 2) - elif is_close_key(key): - return key + if key.is_scroll(): + is_changed = scroller.handle_key(key, len(lines), popup_height - 2)
+ if is_changed: + nyx.curses.draw(_render, top = _top(), width = width, height = height) + elif is_close_key(key): + return key
-def _display_text(fingerprint): + +def _descriptor_text(fingerprint): """ Provides the descriptors for a relay.
@@ -383,68 +418,5 @@ def _display_text(fingerprint): return description.split('\n')
-def _preferred_size(text, max_width, show_line_numbers): - """ - Provides the preferred dimensions of our dialog. - - :param list text: lines of text to be shown - :param int max_width: maximum width the dialog can be - :param bool show_line_numbers: if we should leave room for line numbers - - :returns: **tuple** of the preferred (height, width) - """ - - width, height = 0, len(text) + 2 - line_number_width = int(math.log10(len(text))) + 2 if show_line_numbers else 0 - max_content_width = max_width - line_number_width - 4 - - for line in text: - width = min(max_width, max(width, len(line) + line_number_width + 4)) - height += len(line) / max_content_width # extra lines due to text wrap - - return (height, width) - - -def _draw(popup, title, lines, entry_color, scroll, show_line_numbers): - popup.win.erase() - - line_number_width = int(math.log10(len(lines))) + 1 - in_block = False # flag indicating if we're currently in crypto content - width = popup.max_x - 2 # leave space on the right for the border and an empty line - height = popup.max_y - 2 # height of the dialog without the top and bottom border - offset = line_number_width + 3 if show_line_numbers else 2 - - y = 1 - - for i, line in enumerate(lines): - keyword, value = line, '' - color = entry_color - - if line in HEADERS: - color = HEADER_COLOR - elif line.startswith(BLOCK_START): - in_block = True - elif line.startswith(BLOCK_END): - in_block = False - elif in_block: - keyword, value = '', line - elif ' ' in line and line != UNRESOLVED_MSG and line != ERROR_MSG: - keyword, value = line.split(' ', 1) - - if i < scroll: - continue - - if show_line_numbers: - popup.addstr(y, 2, str(i + 1).rjust(line_number_width), LINE_NUMBER_COLOR, BOLD) - - x, y = popup.addstr_wrap(y, 3 + line_number_width, keyword, width, offset, color, BOLD) - x, y = popup.addstr_wrap(y, x + 1, value, width, offset, color) - - y += 1 - - if y > height: - break - - popup.draw_box() - popup.addstr(0, 0, title, HIGHLIGHT) - popup.win.refresh() +def _top(): + return nyx.controller.get_controller().header_panel().get_height() diff --git a/test/popups.py b/test/popups.py index f3d4963..2a308f0 100644 --- a/test/popups.py +++ b/test/popups.py @@ -53,7 +53,7 @@ Client Locales-----------------------------------------------------------------+ +------------------------------------------------------------------------------+ """.strip()
-EXPECTED_SELECTOR = """ +EXPECTED_LIST_SELECTOR = """ Update Interval:---+ | > each second | | 5 seconds | @@ -90,9 +90,72 @@ Config Option Ordering:--------------------------------------------------------+ +------------------------------------------------------------------------------+ """.strip()
+EXPECTED_DESCRIPTOR_WITHOUT_FINGERPRINT = """ +Consensus Descriptor:----------+ +| No consensus data available | ++------------------------------+ +""".strip() + +DESCRIPTOR_TEXT = """ +Consensus: + +r cyberphunk KXh3YBRc0aRzVSovxkxyqaEwgg4 VjdJThHuYj0jDY2tkkDJkCa8s1s 2016-04-04 19:03:16 94.23.150.191 8080 0 +s Fast Guard Running Stable Valid +w Bandwidth=8410 +p reject 1-65535 + +Server Descriptor: + +router cyberphunk 94.23.150.191 8080 0 0 +platform Tor 0.2.4.27 on Linux +protocols Link 1 2 Circuit 1 +published 2016-04-04 19:03:16 +fingerprint 2978 7760 145C D1A4 7355 2A2F C64C 72A9 A130 820E +uptime 3899791 +bandwidth 10240000 10444800 6482376 +extra-info-digest 9DC532664DDFD238A4119D623D30F136A3B851BF +reject *:* +router-signature +-----BEGIN SIGNATURE----- +EUFm38gONCoDuY7ZWHyJtBKuvk6Xi1MPuKuecS5frP3fX0wiZSrOVcpX0X8J+4Hr +Fb5i+yuMIAXeEn6UhtjqhhZBbY9PW9GdZOMTH8hJpG+evURyr+10PZq6UElg86rA +NCGI042p6+7UgCVT1x3WcLnq3ScV//s1wXHrUXa7vi0= +-----END SIGNATURE----- +""".strip().split('\n') + +EXPECTED_DESCRIPTOR = """ +Consensus Descriptor (29787760145CD1A473552A2FC64C72A9A130820E):---------------------------------------------------+ +| 1 Consensus: | +| 2 | +| 3 r cyberphunk KXh3YBRc0aRzVSovxkxyqaEwgg4 VjdJThHuYj0jDY2tkkDJkCa8s1s 2016-04-04 19:03:16 94.23.150.191 8080 0 | +| 4 s Fast Guard Running Stable Valid | +| 5 w Bandwidth=8410 | +| 6 p reject 1-65535 | +| 7 | +| 8 Server Descriptor: | +| 9 | +| 10 router cyberphunk 94.23.150.191 8080 0 0 | +| 11 platform Tor 0.2.4.27 on Linux | +| 12 protocols Link 1 2 Circuit 1 | +| 13 published 2016-04-04 19:03:16 | +| 14 fingerprint 2978 7760 145C D1A4 7355 2A2F C64C 72A9 A130 820E | +| 15 uptime 3899791 | +| 16 bandwidth 10240000 10444800 6482376 | +| 17 extra-info-digest 9DC532664DDFD238A4119D623D30F136A3B851BF | +| 18 reject *:* | +| 19 router-signature | +| 20 -----BEGIN SIGNATURE----- | +| 21 EUFm38gONCoDuY7ZWHyJtBKuvk6Xi1MPuKuecS5frP3fX0wiZSrOVcpX0X8J+4Hr | +| 22 Fb5i+yuMIAXeEn6UhtjqhhZBbY9PW9GdZOMTH8hJpG+evURyr+10PZq6UElg86rA | +| 23 NCGI042p6+7UgCVT1x3WcLnq3ScV//s1wXHrUXa7vi0= | +| 24 -----END SIGNATURE----- | ++------------------------------------------------------------------------------------------------------------------+ +""".strip() +
class TestPopups(unittest.TestCase): @patch('nyx.controller.get_controller') + @patch('nyx.popups._top', Mock(return_value = 0)) def test_help(self, get_controller_mock): header_panel = Mock()
@@ -121,30 +184,23 @@ class TestPopups(unittest.TestCase): nyx.panel.KeyHandler('c', 'clear event log'), )
- get_controller_mock().header_panel().get_height.return_value = 0 get_controller_mock().get_display_panels.return_value = [header_panel, graph_panel, log_panel]
rendered = test.render(nyx.popups.show_help) self.assertEqual(EXPECTED_HELP_POPUP, rendered.content)
- @patch('nyx.controller.get_controller') - def test_about(self, get_controller_mock): - get_controller_mock().header_panel().get_height.return_value = 0 - + @patch('nyx.popups._top', Mock(return_value = 0)) + def test_about(self): rendered = test.render(nyx.popups.show_about) self.assertEqual(EXPECTED_ABOUT_POPUP, rendered.content)
- @patch('nyx.controller.get_controller') - def test_counts_when_empty(self, get_controller_mock): - get_controller_mock().header_panel().get_height.return_value = 0 - + @patch('nyx.popups._top', Mock(return_value = 0)) + def test_counts_when_empty(self): rendered = test.render(nyx.popups.show_counts, 'Client Locales', {}) self.assertEqual(EXPECTED_EMPTY_COUNTS, rendered.content)
- @patch('nyx.controller.get_controller') - def test_counts(self, get_controller_mock): - get_controller_mock().header_panel().get_height.return_value = 0 - + @patch('nyx.popups._top', Mock(return_value = 0)) + def test_counts(self): clients = { 'fr': 5, 'us': 6, @@ -156,19 +212,15 @@ class TestPopups(unittest.TestCase): rendered = test.render(nyx.popups.show_counts, 'Client Locales', clients, fill_char = '*') self.assertEqual(EXPECTED_COUNTS, rendered.content)
- @patch('nyx.controller.get_controller') - def test_selector(self, get_controller_mock): - get_controller_mock().header_panel().get_height.return_value = 0 - + @patch('nyx.popups._top', Mock(return_value = 0)) + def test_selector(self): options = ['each second', '5 seconds', '30 seconds', 'minutely', '15 minute', '30 minute', 'hourly', 'daily'] - rendered = test.render(nyx.popups.show_selector, 'Update Interval:', options, 'each second') - self.assertEqual(EXPECTED_SELECTOR, rendered.content) + rendered = test.render(nyx.popups.show_list_selector, 'Update Interval:', options, 'each second') + self.assertEqual(EXPECTED_LIST_SELECTOR, rendered.content) self.assertEqual('each second', rendered.return_value)
- @patch('nyx.controller.get_controller') - def test_sort_dialog(self, get_controller_mock): - get_controller_mock().header_panel().get_height.return_value = 0 - + @patch('nyx.popups._top', Mock(return_value = 0)) + def test_sort_dialog(self): previous_order = ['Man Page Entry', 'Name', 'Is Set'] options = ['Name', 'Value', 'Value Type', 'Category', 'Usage', 'Summary', 'Description', 'Man Page Entry', 'Is Set']
@@ -176,8 +228,8 @@ class TestPopups(unittest.TestCase): self.assertEqual(EXPECTED_SORT_DIALOG_START, rendered.content) self.assertEqual(None, rendered.return_value)
- @patch('nyx.controller.get_controller') - def test_sort_dialog_selecting(self, get_controller_mock): + @patch('nyx.popups._top', Mock(return_value = 0)) + def test_sort_dialog_selecting(self): # Use the dialog to make a selection. At the end we render two options as # being selected (rather than three) because the act of selecing the third # closed the popup. @@ -193,11 +245,22 @@ class TestPopups(unittest.TestCase): with patch('nyx.curses.key_input', side_effect = keypresses): return nyx.popups.show_sort_dialog('Config Option Ordering:', options, previous_order, {})
- get_controller_mock().header_panel().get_height.return_value = 0 - previous_order = ['Man Page Entry', 'Name', 'Is Set'] options = ['Name', 'Value', 'Value Type', 'Category', 'Usage', 'Summary', 'Description', 'Man Page Entry', 'Is Set']
rendered = test.render(draw_func) self.assertEqual(EXPECTED_SORT_DIALOG_END, rendered.content) self.assertEqual(['Name', 'Summary', 'Description'], rendered.return_value) + + @patch('nyx.popups._top', Mock(return_value = 0)) + def test_descriptor_without_fingerprint(self): + rendered = test.render(nyx.popups.show_descriptor, None, nyx.curses.Color.RED, lambda key: key.match('esc')) + self.assertEqual(EXPECTED_DESCRIPTOR_WITHOUT_FINGERPRINT, rendered.content) + self.assertEqual(nyx.curses.KeyInput(27), rendered.return_value) + + @patch('nyx.popups._top', Mock(return_value = 0)) + @patch('nyx.popups._descriptor_text', Mock(return_value = DESCRIPTOR_TEXT)) + def test_descriptor(self): + rendered = test.render(nyx.popups.show_descriptor, '29787760145CD1A473552A2FC64C72A9A130820E', nyx.curses.Color.RED, lambda key: key.match('esc')) + self.assertEqual(EXPECTED_DESCRIPTOR, rendered.content) + self.assertEqual(nyx.curses.KeyInput(27), rendered.return_value)