[tor-commits] [nyx/master] Revise descriptor dialog

atagar at torproject.org atagar at torproject.org
Tue Apr 5 01:47:44 UTC 2016


commit cfaf6953c5a2df4521fb48e464b8415b594d4a11
Author: Damian Johnson <atagar at 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)





More information about the tor-commits mailing list