[tor-commits] [nyx/master] Revise confirmation dialog for saving the torrc

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


commit da49b908fb1ee3dc6a7cbd0e90a5ed76ec890e2c
Author: Damian Johnson <atagar at torproject.org>
Date:   Mon Apr 4 18:14:36 2016 -0700

    Revise confirmation dialog for saving the torrc
    
    Overhauling our last popup. Now that the popup module has everything picking a
    uniform naming scheme.
---
 nyx/panel/config.py     |  60 +++---------
 nyx/panel/connection.py |   4 +-
 nyx/panel/graph.py      |   4 +-
 nyx/panel/log.py        |   4 +-
 nyx/popups.py           | 248 +++++++++++++++++++++++++++++-------------------
 test/popups.py          |  63 +++++++++---
 6 files changed, 219 insertions(+), 164 deletions(-)

diff --git a/nyx/panel/config.py b/nyx/panel/config.py
index 6de9e61..1b991b6 100644
--- a/nyx/panel/config.py
+++ b/nyx/panel/config.py
@@ -17,7 +17,7 @@ import nyx.popups
 import stem.control
 import stem.manual
 
-from nyx.curses import GREEN, CYAN, WHITE, NORMAL, BOLD, HIGHLIGHT
+from nyx.curses import WHITE, NORMAL, BOLD, HIGHLIGHT
 from nyx import DATA_DIR, tor_controller
 
 from stem.util import conf, enum, log, str_tools
@@ -175,7 +175,7 @@ class ConfigPanel(nyx.panel.Panel):
     """
 
     sort_colors = dict([(attr, CONFIG['attr.config.sort_color'].get(attr, WHITE)) for attr in SortAttr])
-    results = nyx.popups.show_sort_dialog('Config Option Ordering:', SortAttr, self._sort_order, sort_colors)
+    results = nyx.popups.select_sort_order('Config Option Ordering:', SortAttr, self._sort_order, sort_colors)
 
     if results:
       self._sort_order = results
@@ -186,53 +186,15 @@ class ConfigPanel(nyx.panel.Panel):
     Confirmation dialog for saving tor's configuration.
     """
 
-    selection, controller = 1, tor_controller()
-    config_text = controller.get_info('config-text', None)
-    config_lines = config_text.splitlines() if config_text else []
-
-    with nyx.popups.popup_window(len(config_lines) + 2) as (popup, width, height):
-      if not popup or height <= 2:
-        return
-
-      while True:
-        height, width = popup.get_preferred_size()  # allow us to be resized
-        popup.win.erase()
-
-        for i, full_line in enumerate(config_lines):
-          line = str_tools.crop(full_line, width - 2)
-          option, arg = line.split(' ', 1) if ' ' in line else (line, '')
-
-          popup.addstr(i + 1, 1, option, GREEN, BOLD)
-          popup.addstr(i + 1, len(option) + 2, arg, CYAN, BOLD)
-
-        x = width - 16
-
-        for i, option in enumerate(['Save', 'Cancel']):
-          x = popup.addstr(height - 2, x, '[')
-          x = popup.addstr(height - 2, x, option, BOLD, HIGHLIGHT if i == selection else NORMAL)
-          x = popup.addstr(height - 2, x, '] ')
-
-        popup.draw_box()
-        popup.addstr(0, 0, 'Torrc to save:', HIGHLIGHT)
-        popup.win.refresh()
-
-        key = nyx.curses.key_input()
-
-        if key.match('left'):
-          selection = max(0, selection - 1)
-        elif key.match('right'):
-          selection = min(1, selection + 1)
-        elif key.is_selection():
-          if selection == 0:
-            try:
-              controller.save_conf()
-              nyx.controller.show_message('Saved configuration to %s' % controller.get_info('config-file', '<unknown>'), HIGHLIGHT, max_wait = 2)
-            except IOError as exc:
-              nyx.controller.show_message('Unable to save configuration (%s)' % exc.strerror, HIGHLIGHT, max_wait = 2)
-
-          break
-        elif key.match('esc'):
-          break  # esc - cancel
+    controller = tor_controller()
+    torrc = controller.get_info('config-text', None)
+
+    if nyx.popups.confirm_save_torrc(torrc):
+      try:
+        controller.save_conf()
+        nyx.controller.show_message('Saved configuration to %s' % controller.get_info('config-file', '<unknown>'), HIGHLIGHT, max_wait = 2)
+      except IOError as exc:
+        nyx.controller.show_message('Unable to save configuration (%s)' % exc.strerror, HIGHLIGHT, max_wait = 2)
 
     self.redraw(True)
 
diff --git a/nyx/panel/connection.py b/nyx/panel/connection.py
index afd1da1..709cf52 100644
--- a/nyx/panel/connection.py
+++ b/nyx/panel/connection.py
@@ -311,7 +311,7 @@ class ConnectionPanel(nyx.panel.Panel, threading.Thread):
     """
 
     sort_colors = dict([(attr, CONFIG['attr.connection.sort_color'].get(attr, WHITE)) for attr in SortAttr])
-    results = nyx.popups.show_sort_dialog('Connection Ordering:', SortAttr, self._sort_order, sort_colors)
+    results = nyx.popups.select_sort_order('Connection Ordering:', SortAttr, self._sort_order, sort_colors)
 
     if results:
       self._sort_order = results
@@ -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_list_selector('Connection Resolver:', options, resolver if resolver else 'auto')
+      selected = nyx.popups.select_from_list('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 cad754b..7c53675 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_list_selector('Graphed Stats:', options, previous_selection)
+      selection = nyx.popups.select_from_list('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_list_selector('Update Interval:', list(Interval), self.update_interval)
+      self.update_interval = nyx.popups.select_from_list('Update Interval:', list(Interval), self.update_interval)
       self.redraw(True)
 
     return (
diff --git a/nyx/panel/log.py b/nyx/panel/log.py
index 364878f..dc26dc3 100644
--- a/nyx/panel/log.py
+++ b/nyx/panel/log.py
@@ -141,7 +141,7 @@ class LogPanel(nyx.panel.Panel, threading.Thread):
     Prompts the user to select the events being listened for.
     """
 
-    event_types = nyx.popups.show_event_selector()
+    event_types = nyx.popups.select_event_types()
 
     if event_types != self._event_types:
       self._event_types = nyx.log.listen_for_events(self._register_tor_event, event_types)
@@ -214,7 +214,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_list_selector('Log Filter:', options, initial_selection)
+        selection = nyx.popups.select_from_list('Log Filter:', options, initial_selection)
 
         if selection == 'None':
           self._filter.select(None)
diff --git a/nyx/popups.py b/nyx/popups.py
index 3160949..b7ad7b1 100644
--- a/nyx/popups.py
+++ b/nyx/popups.py
@@ -2,7 +2,20 @@
 # See LICENSE for licensing information
 
 """
-Functions for displaying popups in the interface.
+Popup dialogs provided by our interface.
+
+::
+
+  show_help - keybindings provided by the current page
+  show_about - basic information about our application
+  show_counts - listing of counts with bar graphs
+  show_descriptor - presents descriptors for a relay
+
+  select_from_list - selects from a list of options
+  select_sort_order - selects attributes by which to sort by
+  select_event_types - select from a list of event types
+
+  confirm_save_torrc - confirmation dialog for saving the torrc
 """
 
 import math
@@ -204,7 +217,117 @@ def show_counts(title, counts, fill_char = ' '):
     nyx.curses.key_input()
 
 
-def show_list_selector(title, options, previous_selection):
+def show_descriptor(fingerprint, color, is_close_key):
+  """
+  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 function is_close_key: method to indicate if a key should close the
+    dialog or not
+
+  :returns: :class:`~nyx.curses.KeyInput` for the keyboard input that
+    closed the dialog
+  """
+
+  if fingerprint:
+    title = 'Consensus Descriptor (%s):' % fingerprint
+    lines = _descriptor_text(fingerprint)
+    show_line_numbers = True
+  else:
+    title = 'Consensus Descriptor:'
+    lines = [UNRESOLVED_MSG]
+    show_line_numbers = False
+
+  scroller = nyx.curses.Scroller()
+  line_number_width = int(math.log10(len(lines))) + 1 if show_line_numbers else 0
+
+  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
+
+    subwindow.box()
+    subwindow.addstr(0, 0, title, HIGHLIGHT)
+
+  width, height = 0, len(lines) + 2
+  screen_size = nyx.curses.screen_size()
+
+  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
+
+  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():
+        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 _descriptor_text(fingerprint):
+  """
+  Provides the descriptors for a relay.
+
+  :param str fingerprint: relay fingerprint to be looked up
+
+  :returns: **list** with the lines that should be displayed in the dialog
+  """
+
+  controller = nyx.tor_controller()
+  router_status_entry = controller.get_network_status(fingerprint, None)
+  microdescriptor = controller.get_microdescriptor(fingerprint, None)
+  server_descriptor = controller.get_server_descriptor(fingerprint, None)
+
+  description = 'Consensus:\n\n%s' % (router_status_entry if router_status_entry else ERROR_MSG)
+
+  if server_descriptor:
+    description += '\n\nServer Descriptor:\n\n%s' % server_descriptor
+
+  if microdescriptor:
+    description += '\n\nMicrodescriptor:\n\n%s' % microdescriptor
+
+  return description.split('\n')
+
+
+def select_from_list(title, options, previous_selection):
   """
   Provides list of items the user can choose from.
 
@@ -244,7 +367,7 @@ def show_list_selector(title, options, previous_selection):
         return previous_selection
 
 
-def show_sort_dialog(title, options, previous_order, option_colors):
+def select_sort_order(title, options, previous_order, option_colors):
   """
   Provides sorting dialog of the form...
 
@@ -315,7 +438,7 @@ def show_sort_dialog(title, options, previous_order, option_colors):
   return new_order
 
 
-def show_event_selector():
+def select_event_types():
   """
   Presents a chart of event types we support, with a prompt for the user to
   select a set.
@@ -345,114 +468,49 @@ def show_event_selector():
     return None
 
 
-def show_descriptor(fingerprint, color, is_close_key):
+def confirm_save_torrc(torrc):
   """
-  Provides a dialog showing descriptors for a relay.
+  Provides a confirmation dialog for saving tor's current configuration.
 
-  :param str fingerprint: fingerprint of the relay to be shown
-  :param str color: text color of the dialog
-  :param function is_close_key: method to indicate if a key should close the
-    dialog or not
+  :param str torrc: torrc that would be saved
 
-  :returns: :class:`~nyx.curses.KeyInput` for the keyboard input that
-    closed the dialog
+  :returns: **True** if the torrc should be saved and **False** otherwise
   """
 
-  if fingerprint:
-    title = 'Consensus Descriptor (%s):' % fingerprint
-    lines = _descriptor_text(fingerprint)
-    show_line_numbers = True
-  else:
-    title = 'Consensus Descriptor:'
-    lines = [UNRESOLVED_MSG]
-    show_line_numbers = False
-
-  scroller = nyx.curses.Scroller()
-  line_number_width = int(math.log10(len(lines))) + 1 if show_line_numbers else 0
+  torrc_lines = torrc.splitlines() if torrc else []
+  selection = 1
 
   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 + ' '
+    for i, full_line in enumerate(torrc_lines):
+      line = stem.util.str_tools.crop(full_line, subwindow.width - 2)
+      option, arg = line.split(' ', 1) if ' ' in line else (line, '')
 
-      if i < scroller.location():
-        continue
-
-      if show_line_numbers:
-        subwindow.addstr(2, y, str(i + 1).rjust(line_number_width), LINE_NUMBER_COLOR, BOLD)
+      subwindow.addstr(1, i + 1, option, GREEN, BOLD)
+      subwindow.addstr(len(option) + 2, i + 1, arg, CYAN, 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
+    x = subwindow.width - 16
 
-      if y > subwindow.height - 2:
-        break
+    for i, option in enumerate(['Save', 'Cancel']):
+      x = subwindow.addstr(x, subwindow.height - 2, '[')
+      x = subwindow.addstr(x, subwindow.height - 2, option, BOLD, HIGHLIGHT if i == selection else NORMAL)
+      x = subwindow.addstr(x, subwindow.height - 2, '] ')
 
     subwindow.box()
-    subwindow.addstr(0, 0, title, HIGHLIGHT)
-
-  width, height = 0, len(lines) + 2
-  screen_size = nyx.curses.screen_size()
-
-  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
+    subwindow.addstr(0, 0, 'Torrc to save:', HIGHLIGHT)
 
   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:
+      nyx.curses.draw(_render, top = _top(), height = len(torrc_lines) + 2)
       key = nyx.curses.key_input()
 
-      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 _descriptor_text(fingerprint):
-  """
-  Provides the descriptors for a relay.
-
-  :param str fingerprint: relay fingerprint to be looked up
-
-  :returns: **list** with the lines that should be displayed in the dialog
-  """
-
-  controller = nyx.tor_controller()
-  router_status_entry = controller.get_network_status(fingerprint, None)
-  microdescriptor = controller.get_microdescriptor(fingerprint, None)
-  server_descriptor = controller.get_server_descriptor(fingerprint, None)
-
-  description = 'Consensus:\n\n%s' % (router_status_entry if router_status_entry else ERROR_MSG)
-
-  if server_descriptor:
-    description += '\n\nServer Descriptor:\n\n%s' % server_descriptor
-
-  if microdescriptor:
-    description += '\n\nMicrodescriptor:\n\n%s' % microdescriptor
-
-  return description.split('\n')
+      if key.match('left'):
+        selection = max(0, selection - 1)
+      elif key.match('right'):
+        selection = min(1, selection + 1)
+      elif key.is_selection():
+        return selection == 0
+      elif key.match('esc'):
+        return False  # esc - cancel
 
 
 def _top():
diff --git a/test/popups.py b/test/popups.py
index e34398b..34ff587 100644
--- a/test/popups.py
+++ b/test/popups.py
@@ -109,10 +109,24 @@ Event Types:-------------------------------------------------------------------+
 +------------------------------------------------------------------------------+
 """.strip()
 
-EXPECTED_DESCRIPTOR_WITHOUT_FINGERPRINT = """
-Consensus Descriptor:----------+
-|  No consensus data available |
-+------------------------------+
+TORRC = """
+ControlPort 9051
+CookieAuthentication 1
+ExitPolicy reject *:*
+DataDirectory /home/atagar/.tor
+Log notice file /home/atagar/.tor/log
+ORPort 7000
+""".strip()
+
+EXPECTED_SAVE_TORRC_CONFIRMATION = """
+Torrc to save:-----------------------------------------------------------------+
+|ControlPort 9051                                                              |
+|CookieAuthentication 1                                                        |
+|ExitPolicy reject *:*                                                         |
+|DataDirectory /home/atagar/.tor                                               |
+|Log notice file /home/atagar/.tor/log                                         |
+|ORPort 7000                                                    [Save] [Cancel]|
++------------------------------------------------------------------------------+
 """.strip()
 
 DESCRIPTOR_TEXT = """
@@ -142,6 +156,12 @@ NCGI042p6+7UgCVT1x3WcLnq3ScV//s1wXHrUXa7vi0=
 -----END SIGNATURE-----
 """.strip().split('\n')
 
+EXPECTED_DESCRIPTOR_WITHOUT_FINGERPRINT = """
+Consensus Descriptor:----------+
+|  No consensus data available |
++------------------------------+
+""".strip()
+
 EXPECTED_DESCRIPTOR = """
 Consensus Descriptor (29787760145CD1A473552A2FC64C72A9A130820E):---------------------------------------------------+
 |  1 Consensus:                                                                                                    |
@@ -232,23 +252,23 @@ class TestPopups(unittest.TestCase):
     self.assertEqual(EXPECTED_COUNTS, rendered.content)
 
   @patch('nyx.popups._top', Mock(return_value = 0))
-  def test_selector(self):
+  def test_select_from_list(self):
     options = ['each second', '5 seconds', '30 seconds', 'minutely', '15 minute', '30 minute', 'hourly', 'daily']
-    rendered = test.render(nyx.popups.show_list_selector, 'Update Interval:', options, 'each second')
+    rendered = test.render(nyx.popups.select_from_list, 'Update Interval:', options, 'each second')
     self.assertEqual(EXPECTED_LIST_SELECTOR, rendered.content)
     self.assertEqual('each second', rendered.return_value)
 
   @patch('nyx.popups._top', Mock(return_value = 0))
-  def test_sort_dialog(self):
+  def test_select_sort_order(self):
     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(nyx.popups.show_sort_dialog, 'Config Option Ordering:', options, previous_order, {})
+    rendered = test.render(nyx.popups.select_sort_order, 'Config Option Ordering:', options, previous_order, {})
     self.assertEqual(EXPECTED_SORT_DIALOG_START, rendered.content)
     self.assertEqual(None, rendered.return_value)
 
   @patch('nyx.popups._top', Mock(return_value = 0))
-  def test_sort_dialog_selecting(self):
+  def test_select_sort_order_usage(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.
@@ -262,7 +282,7 @@ class TestPopups(unittest.TestCase):
 
     def draw_func():
       with patch('nyx.curses.key_input', side_effect = keypresses):
-        return nyx.popups.show_sort_dialog('Config Option Ordering:', options, previous_order, {})
+        return nyx.popups.select_sort_order('Config Option Ordering:', options, previous_order, {})
 
     previous_order = ['Man Page Entry', 'Name', 'Is Set']
     options = ['Name', 'Value', 'Value Type', 'Category', 'Usage', 'Summary', 'Description', 'Man Page Entry', 'Is Set']
@@ -273,18 +293,33 @@ class TestPopups(unittest.TestCase):
 
   @patch('nyx.popups._top', Mock(return_value = 0))
   @patch('nyx.controller.input_prompt', Mock(return_value = None))
-  def test_event_selector_when_canceled(self):
-    rendered = test.render(nyx.popups.show_event_selector)
+  def test_select_event_types_when_canceled(self):
+    rendered = test.render(nyx.popups.select_event_types)
     self.assertEqual(EXPECTED_EVENT_SELECTOR, rendered.content)
     self.assertEqual(None, rendered.return_value)
 
   @patch('nyx.popups._top', Mock(return_value = 0))
   @patch('nyx.controller.input_prompt', Mock(return_value = '2bwe'))
-  def test_event_selector_with_input(self):
-    rendered = test.render(nyx.popups.show_event_selector)
+  def test_select_event_types_with_input(self):
+    rendered = test.render(nyx.popups.select_event_types)
     self.assertEqual(EXPECTED_EVENT_SELECTOR, rendered.content)
     self.assertEqual(set(['NYX_INFO', 'ERR', 'WARN', 'BW', 'NYX_ERR', 'NYX_WARN', 'NYX_NOTICE']), rendered.return_value)
 
+  @patch('nyx.curses.screen_size', Mock(return_value = nyx.curses.Dimensions(80, 60)))
+  @patch('nyx.popups._top', Mock(return_value = 0))
+  def test_confirm_save_torrc(self):
+    rendered = test.render(nyx.popups.confirm_save_torrc, TORRC)
+    self.assertEqual(EXPECTED_SAVE_TORRC_CONFIRMATION, rendered.content)
+    self.assertEqual(False, rendered.return_value)
+
+    def draw_func():
+      with patch('nyx.curses.key_input', side_effect = [nyx.curses.KeyInput(curses.KEY_LEFT), nyx.curses.KeyInput(curses.KEY_ENTER)]):
+        return nyx.popups.confirm_save_torrc(TORRC)
+
+    rendered = test.render(draw_func)
+    self.assertEqual(EXPECTED_SAVE_TORRC_CONFIRMATION, rendered.content)
+    self.assertEqual(True, 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'))





More information about the tor-commits mailing list