(root)/
Python-3.12.0/
Lib/
idlelib/
idle_test/
test_configdialog.py
       1  """Test configdialog, coverage 94%.
       2  
       3  Half the class creates dialog, half works with user customizations.
       4  """
       5  from idlelib import configdialog
       6  from test.support import requires
       7  requires('gui')
       8  import unittest
       9  from unittest import mock
      10  from idlelib.idle_test.mock_idle import Func
      11  from tkinter import (Tk, StringVar, IntVar, BooleanVar, DISABLED, NORMAL)
      12  from idlelib import config
      13  from idlelib.configdialog import idleConf, changes, tracers
      14  
      15  # Tests should not depend on fortuitous user configurations.
      16  # They must not affect actual user .cfg files.
      17  # Use solution from test_config: empty parsers with no filename.
      18  usercfg = idleConf.userCfg
      19  testcfg = {
      20      'main': config.IdleUserConfParser(''),
      21      'highlight': config.IdleUserConfParser(''),
      22      'keys': config.IdleUserConfParser(''),
      23      'extensions': config.IdleUserConfParser(''),
      24  }
      25  
      26  root = None
      27  dialog = None
      28  mainpage = changes['main']
      29  highpage = changes['highlight']
      30  keyspage = changes['keys']
      31  extpage = changes['extensions']
      32  
      33  
      34  def setUpModule():
      35      global root, dialog
      36      idleConf.userCfg = testcfg
      37      root = Tk()
      38      # root.withdraw()    # Comment out, see issue 30870
      39      dialog = configdialog.ConfigDialog(root, 'Test', _utest=True)
      40  
      41  
      42  def tearDownModule():
      43      global root, dialog
      44      idleConf.userCfg = usercfg
      45      tracers.detach()
      46      tracers.clear()
      47      changes.clear()
      48      root.update_idletasks()
      49      root.destroy()
      50      root = dialog = None
      51  
      52  
      53  class ESC[4;38;5;81mConfigDialogTest(ESC[4;38;5;149munittestESC[4;38;5;149m.ESC[4;38;5;149mTestCase):
      54  
      55      def test_deactivate_current_config(self):
      56          pass
      57  
      58      def activate_config_changes(self):
      59          pass
      60  
      61  
      62  class ESC[4;38;5;81mButtonTest(ESC[4;38;5;149munittestESC[4;38;5;149m.ESC[4;38;5;149mTestCase):
      63  
      64      def test_click_ok(self):
      65          d = dialog
      66          apply = d.apply = mock.Mock()
      67          destroy = d.destroy = mock.Mock()
      68          d.buttons['Ok'].invoke()
      69          apply.assert_called_once()
      70          destroy.assert_called_once()
      71          del d.destroy, d.apply
      72  
      73      def test_click_apply(self):
      74          d = dialog
      75          deactivate = d.deactivate_current_config = mock.Mock()
      76          save_ext = d.extpage.save_all_changed_extensions = mock.Mock()
      77          activate = d.activate_config_changes = mock.Mock()
      78          d.buttons['Apply'].invoke()
      79          deactivate.assert_called_once()
      80          save_ext.assert_called_once()
      81          activate.assert_called_once()
      82          del d.extpage.save_all_changed_extensions
      83          del d.activate_config_changes, d.deactivate_current_config
      84  
      85      def test_click_cancel(self):
      86          d = dialog
      87          d.destroy = Func()
      88          changes['main']['something'] = 1
      89          d.buttons['Cancel'].invoke()
      90          self.assertEqual(changes['main'], {})
      91          self.assertEqual(d.destroy.called, 1)
      92          del d.destroy
      93  
      94      def test_click_help(self):
      95          dialog.note.select(dialog.keyspage)
      96          with mock.patch.object(configdialog, 'view_text',
      97                                 new_callable=Func) as view:
      98              dialog.buttons['Help'].invoke()
      99              title, contents = view.kwds['title'], view.kwds['contents']
     100          self.assertEqual(title, 'Help for IDLE preferences')
     101          self.assertTrue(contents.startswith('When you click') and
     102                          contents.endswith('a different name.\n'))
     103  
     104  
     105  class ESC[4;38;5;81mFontPageTest(ESC[4;38;5;149munittestESC[4;38;5;149m.ESC[4;38;5;149mTestCase):
     106      """Test that font widgets enable users to make font changes.
     107  
     108      Test that widget actions set vars, that var changes add three
     109      options to changes and call set_samples, and that set_samples
     110      changes the font of both sample boxes.
     111      """
     112      @classmethod
     113      def setUpClass(cls):
     114          page = cls.page = dialog.fontpage
     115          dialog.note.select(page)
     116          page.set_samples = Func()  # Mask instance method.
     117          page.update()
     118  
     119      @classmethod
     120      def tearDownClass(cls):
     121          del cls.page.set_samples  # Unmask instance method.
     122  
     123      def setUp(self):
     124          changes.clear()
     125  
     126      def test_load_font_cfg(self):
     127          # Leave widget load test to human visual check.
     128          # TODO Improve checks when add IdleConf.get_font_values.
     129          tracers.detach()
     130          d = self.page
     131          d.font_name.set('Fake')
     132          d.font_size.set('1')
     133          d.font_bold.set(True)
     134          d.set_samples.called = 0
     135          d.load_font_cfg()
     136          self.assertNotEqual(d.font_name.get(), 'Fake')
     137          self.assertNotEqual(d.font_size.get(), '1')
     138          self.assertFalse(d.font_bold.get())
     139          self.assertEqual(d.set_samples.called, 1)
     140          tracers.attach()
     141  
     142      def test_fontlist_key(self):
     143          # Up and Down keys should select a new font.
     144          d = self.page
     145          if d.fontlist.size() < 2:
     146              self.skipTest('need at least 2 fonts')
     147          fontlist = d.fontlist
     148          fontlist.activate(0)
     149          font = d.fontlist.get('active')
     150  
     151          # Test Down key.
     152          fontlist.focus_force()
     153          fontlist.update()
     154          fontlist.event_generate('<Key-Down>')
     155          fontlist.event_generate('<KeyRelease-Down>')
     156  
     157          down_font = fontlist.get('active')
     158          self.assertNotEqual(down_font, font)
     159          self.assertIn(d.font_name.get(), down_font.lower())
     160  
     161          # Test Up key.
     162          fontlist.focus_force()
     163          fontlist.update()
     164          fontlist.event_generate('<Key-Up>')
     165          fontlist.event_generate('<KeyRelease-Up>')
     166  
     167          up_font = fontlist.get('active')
     168          self.assertEqual(up_font, font)
     169          self.assertIn(d.font_name.get(), up_font.lower())
     170  
     171      def test_fontlist_mouse(self):
     172          # Click on item should select that item.
     173          d = self.page
     174          if d.fontlist.size() < 2:
     175              self.skipTest('need at least 2 fonts')
     176          fontlist = d.fontlist
     177          fontlist.activate(0)
     178  
     179          # Select next item in listbox
     180          fontlist.focus_force()
     181          fontlist.see(1)
     182          fontlist.update()
     183          x, y, dx, dy = fontlist.bbox(1)
     184          x += dx // 2
     185          y += dy // 2
     186          fontlist.event_generate('<Button-1>', x=x, y=y)
     187          fontlist.event_generate('<ButtonRelease-1>', x=x, y=y)
     188  
     189          font1 = fontlist.get(1)
     190          select_font = fontlist.get('anchor')
     191          self.assertEqual(select_font, font1)
     192          self.assertIn(d.font_name.get(), font1.lower())
     193  
     194      def test_sizelist(self):
     195          # Click on number should select that number
     196          d = self.page
     197          d.sizelist.variable.set(40)
     198          self.assertEqual(d.font_size.get(), '40')
     199  
     200      def test_bold_toggle(self):
     201          # Click on checkbutton should invert it.
     202          d = self.page
     203          d.font_bold.set(False)
     204          d.bold_toggle.invoke()
     205          self.assertTrue(d.font_bold.get())
     206          d.bold_toggle.invoke()
     207          self.assertFalse(d.font_bold.get())
     208  
     209      def test_font_set(self):
     210          # Test that setting a font Variable results in 3 provisional
     211          # change entries and a call to set_samples. Use values sure to
     212          # not be defaults.
     213  
     214          default_font = idleConf.GetFont(root, 'main', 'EditorWindow')
     215          default_size = str(default_font[1])
     216          default_bold = default_font[2] == 'bold'
     217          d = self.page
     218          d.font_size.set(default_size)
     219          d.font_bold.set(default_bold)
     220          d.set_samples.called = 0
     221  
     222          d.font_name.set('Test Font')
     223          expected = {'EditorWindow': {'font': 'Test Font',
     224                                       'font-size': default_size,
     225                                       'font-bold': str(default_bold)}}
     226          self.assertEqual(mainpage, expected)
     227          self.assertEqual(d.set_samples.called, 1)
     228          changes.clear()
     229  
     230          d.font_size.set('20')
     231          expected = {'EditorWindow': {'font': 'Test Font',
     232                                       'font-size': '20',
     233                                       'font-bold': str(default_bold)}}
     234          self.assertEqual(mainpage, expected)
     235          self.assertEqual(d.set_samples.called, 2)
     236          changes.clear()
     237  
     238          d.font_bold.set(not default_bold)
     239          expected = {'EditorWindow': {'font': 'Test Font',
     240                                       'font-size': '20',
     241                                       'font-bold': str(not default_bold)}}
     242          self.assertEqual(mainpage, expected)
     243          self.assertEqual(d.set_samples.called, 3)
     244  
     245      def test_set_samples(self):
     246          d = self.page
     247          del d.set_samples  # Unmask method for test
     248          orig_samples = d.font_sample, d.highlight_sample
     249          d.font_sample, d.highlight_sample = {}, {}
     250          d.font_name.set('test')
     251          d.font_size.set('5')
     252          d.font_bold.set(1)
     253          expected = {'font': ('test', '5', 'bold')}
     254  
     255          # Test set_samples.
     256          d.set_samples()
     257          self.assertTrue(d.font_sample == d.highlight_sample == expected)
     258  
     259          d.font_sample, d.highlight_sample = orig_samples
     260          d.set_samples = Func()  # Re-mask for other tests.
     261  
     262  
     263  class ESC[4;38;5;81mHighPageTest(ESC[4;38;5;149munittestESC[4;38;5;149m.ESC[4;38;5;149mTestCase):
     264      """Test that highlight tab widgets enable users to make changes.
     265  
     266      Test that widget actions set vars, that var changes add
     267      options to changes and that themes work correctly.
     268      """
     269  
     270      @classmethod
     271      def setUpClass(cls):
     272          page = cls.page = dialog.highpage
     273          dialog.note.select(page)
     274          page.set_theme_type = Func()
     275          page.paint_theme_sample = Func()
     276          page.set_highlight_target = Func()
     277          page.set_color_sample = Func()
     278          page.update()
     279  
     280      @classmethod
     281      def tearDownClass(cls):
     282          d = cls.page
     283          del d.set_theme_type, d.paint_theme_sample
     284          del d.set_highlight_target, d.set_color_sample
     285  
     286      def setUp(self):
     287          d = self.page
     288          # The following is needed for test_load_key_cfg, _delete_custom_keys.
     289          # This may indicate a defect in some test or function.
     290          for section in idleConf.GetSectionList('user', 'highlight'):
     291              idleConf.userCfg['highlight'].remove_section(section)
     292          changes.clear()
     293          d.set_theme_type.called = 0
     294          d.paint_theme_sample.called = 0
     295          d.set_highlight_target.called = 0
     296          d.set_color_sample.called = 0
     297  
     298      def test_load_theme_cfg(self):
     299          tracers.detach()
     300          d = self.page
     301          eq = self.assertEqual
     302  
     303          # Use builtin theme with no user themes created.
     304          idleConf.CurrentTheme = mock.Mock(return_value='IDLE Classic')
     305          d.load_theme_cfg()
     306          self.assertTrue(d.theme_source.get())
     307          # builtinlist sets variable builtin_name to the CurrentTheme default.
     308          eq(d.builtin_name.get(), 'IDLE Classic')
     309          eq(d.custom_name.get(), '- no custom themes -')
     310          eq(d.custom_theme_on.state(), ('disabled',))
     311          eq(d.set_theme_type.called, 1)
     312          eq(d.paint_theme_sample.called, 1)
     313          eq(d.set_highlight_target.called, 1)
     314  
     315          # Builtin theme with non-empty user theme list.
     316          idleConf.SetOption('highlight', 'test1', 'option', 'value')
     317          idleConf.SetOption('highlight', 'test2', 'option2', 'value2')
     318          d.load_theme_cfg()
     319          eq(d.builtin_name.get(), 'IDLE Classic')
     320          eq(d.custom_name.get(), 'test1')
     321          eq(d.set_theme_type.called, 2)
     322          eq(d.paint_theme_sample.called, 2)
     323          eq(d.set_highlight_target.called, 2)
     324  
     325          # Use custom theme.
     326          idleConf.CurrentTheme = mock.Mock(return_value='test2')
     327          idleConf.SetOption('main', 'Theme', 'default', '0')
     328          d.load_theme_cfg()
     329          self.assertFalse(d.theme_source.get())
     330          eq(d.builtin_name.get(), 'IDLE Classic')
     331          eq(d.custom_name.get(), 'test2')
     332          eq(d.set_theme_type.called, 3)
     333          eq(d.paint_theme_sample.called, 3)
     334          eq(d.set_highlight_target.called, 3)
     335  
     336          del idleConf.CurrentTheme
     337          tracers.attach()
     338  
     339      def test_theme_source(self):
     340          eq = self.assertEqual
     341          d = self.page
     342          # Test these separately.
     343          d.var_changed_builtin_name = Func()
     344          d.var_changed_custom_name = Func()
     345          # Builtin selected.
     346          d.builtin_theme_on.invoke()
     347          eq(mainpage, {'Theme': {'default': 'True'}})
     348          eq(d.var_changed_builtin_name.called, 1)
     349          eq(d.var_changed_custom_name.called, 0)
     350          changes.clear()
     351  
     352          # Custom selected.
     353          d.custom_theme_on.state(('!disabled',))
     354          d.custom_theme_on.invoke()
     355          self.assertEqual(mainpage, {'Theme': {'default': 'False'}})
     356          eq(d.var_changed_builtin_name.called, 1)
     357          eq(d.var_changed_custom_name.called, 1)
     358          del d.var_changed_builtin_name, d.var_changed_custom_name
     359  
     360      def test_builtin_name(self):
     361          eq = self.assertEqual
     362          d = self.page
     363          item_list = ['IDLE Classic', 'IDLE Dark', 'IDLE New']
     364  
     365          # Not in old_themes, defaults name to first item.
     366          idleConf.SetOption('main', 'Theme', 'name', 'spam')
     367          d.builtinlist.SetMenu(item_list, 'IDLE Dark')
     368          eq(mainpage, {'Theme': {'name': 'IDLE Classic',
     369                                  'name2': 'IDLE Dark'}})
     370          eq(d.theme_message['text'], 'New theme, see Help')
     371          eq(d.paint_theme_sample.called, 1)
     372  
     373          # Not in old themes - uses name2.
     374          changes.clear()
     375          idleConf.SetOption('main', 'Theme', 'name', 'IDLE New')
     376          d.builtinlist.SetMenu(item_list, 'IDLE Dark')
     377          eq(mainpage, {'Theme': {'name2': 'IDLE Dark'}})
     378          eq(d.theme_message['text'], 'New theme, see Help')
     379          eq(d.paint_theme_sample.called, 2)
     380  
     381          # Builtin name in old_themes.
     382          changes.clear()
     383          d.builtinlist.SetMenu(item_list, 'IDLE Classic')
     384          eq(mainpage, {'Theme': {'name': 'IDLE Classic', 'name2': ''}})
     385          eq(d.theme_message['text'], '')
     386          eq(d.paint_theme_sample.called, 3)
     387  
     388      def test_custom_name(self):
     389          d = self.page
     390  
     391          # If no selections, doesn't get added.
     392          d.customlist.SetMenu([], '- no custom themes -')
     393          self.assertNotIn('Theme', mainpage)
     394          self.assertEqual(d.paint_theme_sample.called, 0)
     395  
     396          # Custom name selected.
     397          changes.clear()
     398          d.customlist.SetMenu(['a', 'b', 'c'], 'c')
     399          self.assertEqual(mainpage, {'Theme': {'name': 'c'}})
     400          self.assertEqual(d.paint_theme_sample.called, 1)
     401  
     402      def test_color(self):
     403          d = self.page
     404          d.on_new_color_set = Func()
     405          # self.color is only set in get_color through colorchooser.
     406          d.color.set('green')
     407          self.assertEqual(d.on_new_color_set.called, 1)
     408          del d.on_new_color_set
     409  
     410      def test_highlight_target_list_mouse(self):
     411          # Set highlight_target through targetlist.
     412          eq = self.assertEqual
     413          d = self.page
     414  
     415          d.targetlist.SetMenu(['a', 'b', 'c'], 'c')
     416          eq(d.highlight_target.get(), 'c')
     417          eq(d.set_highlight_target.called, 1)
     418  
     419      def test_highlight_target_text_mouse(self):
     420          # Set highlight_target through clicking highlight_sample.
     421          eq = self.assertEqual
     422          d = self.page
     423  
     424          elem = {}
     425          count = 0
     426          hs = d.highlight_sample
     427          hs.focus_force()
     428          hs.see(1.0)
     429          hs.update_idletasks()
     430  
     431          def tag_to_element(elem):
     432              for element, tag in d.theme_elements.items():
     433                  elem[tag[0]] = element
     434  
     435          def click_it(start):
     436              x, y, dx, dy = hs.bbox(start)
     437              x += dx // 2
     438              y += dy // 2
     439              hs.event_generate('<Enter>', x=0, y=0)
     440              hs.event_generate('<Motion>', x=x, y=y)
     441              hs.event_generate('<ButtonPress-1>', x=x, y=y)
     442              hs.event_generate('<ButtonRelease-1>', x=x, y=y)
     443  
     444          # Flip theme_elements to make the tag the key.
     445          tag_to_element(elem)
     446  
     447          # If highlight_sample has a tag that isn't in theme_elements, there
     448          # will be a KeyError in the test run.
     449          for tag in hs.tag_names():
     450              for start_index in hs.tag_ranges(tag)[0::2]:
     451                  count += 1
     452                  click_it(start_index)
     453                  eq(d.highlight_target.get(), elem[tag])
     454                  eq(d.set_highlight_target.called, count)
     455  
     456      def test_highlight_sample_double_click(self):
     457          # Test double click on highlight_sample.
     458          eq = self.assertEqual
     459          d = self.page
     460  
     461          hs = d.highlight_sample
     462          hs.focus_force()
     463          hs.see(1.0)
     464          hs.update_idletasks()
     465  
     466          # Test binding from configdialog.
     467          hs.event_generate('<Enter>', x=0, y=0)
     468          hs.event_generate('<Motion>', x=0, y=0)
     469          # Double click is a sequence of two clicks in a row.
     470          for _ in range(2):
     471              hs.event_generate('<ButtonPress-1>', x=0, y=0)
     472              hs.event_generate('<ButtonRelease-1>', x=0, y=0)
     473  
     474          eq(hs.tag_ranges('sel'), ())
     475  
     476      def test_highlight_sample_b1_motion(self):
     477          # Test button motion on highlight_sample.
     478          eq = self.assertEqual
     479          d = self.page
     480  
     481          hs = d.highlight_sample
     482          hs.focus_force()
     483          hs.see(1.0)
     484          hs.update_idletasks()
     485  
     486          x, y, dx, dy, offset = hs.dlineinfo('1.0')
     487  
     488          # Test binding from configdialog.
     489          hs.event_generate('<Leave>')
     490          hs.event_generate('<Enter>')
     491          hs.event_generate('<Motion>', x=x, y=y)
     492          hs.event_generate('<ButtonPress-1>', x=x, y=y)
     493          hs.event_generate('<B1-Motion>', x=dx, y=dy)
     494          hs.event_generate('<ButtonRelease-1>', x=dx, y=dy)
     495  
     496          eq(hs.tag_ranges('sel'), ())
     497  
     498      def test_set_theme_type(self):
     499          eq = self.assertEqual
     500          d = self.page
     501          del d.set_theme_type
     502  
     503          # Builtin theme selected.
     504          d.theme_source.set(True)
     505          d.set_theme_type()
     506          eq(d.builtinlist['state'], NORMAL)
     507          eq(d.customlist['state'], DISABLED)
     508          eq(d.button_delete_custom.state(), ('disabled',))
     509  
     510          # Custom theme selected.
     511          d.theme_source.set(False)
     512          d.set_theme_type()
     513          eq(d.builtinlist['state'], DISABLED)
     514          eq(d.custom_theme_on.state(), ('selected',))
     515          eq(d.customlist['state'], NORMAL)
     516          eq(d.button_delete_custom.state(), ())
     517          d.set_theme_type = Func()
     518  
     519      def test_get_color(self):
     520          eq = self.assertEqual
     521          d = self.page
     522          orig_chooser = configdialog.colorchooser.askcolor
     523          chooser = configdialog.colorchooser.askcolor = Func()
     524          gntn = d.get_new_theme_name = Func()
     525  
     526          d.highlight_target.set('Editor Breakpoint')
     527          d.color.set('#ffffff')
     528  
     529          # Nothing selected.
     530          chooser.result = (None, None)
     531          d.button_set_color.invoke()
     532          eq(d.color.get(), '#ffffff')
     533  
     534          # Selection same as previous color.
     535          chooser.result = ('', d.style.lookup(d.frame_color_set['style'], 'background'))
     536          d.button_set_color.invoke()
     537          eq(d.color.get(), '#ffffff')
     538  
     539          # Select different color.
     540          chooser.result = ((222.8671875, 0.0, 0.0), '#de0000')
     541  
     542          # Default theme.
     543          d.color.set('#ffffff')
     544          d.theme_source.set(True)
     545  
     546          # No theme name selected therefore color not saved.
     547          gntn.result = ''
     548          d.button_set_color.invoke()
     549          eq(gntn.called, 1)
     550          eq(d.color.get(), '#ffffff')
     551          # Theme name selected.
     552          gntn.result = 'My New Theme'
     553          d.button_set_color.invoke()
     554          eq(d.custom_name.get(), gntn.result)
     555          eq(d.color.get(), '#de0000')
     556  
     557          # Custom theme.
     558          d.color.set('#ffffff')
     559          d.theme_source.set(False)
     560          d.button_set_color.invoke()
     561          eq(d.color.get(), '#de0000')
     562  
     563          del d.get_new_theme_name
     564          configdialog.colorchooser.askcolor = orig_chooser
     565  
     566      def test_on_new_color_set(self):
     567          d = self.page
     568          color = '#3f7cae'
     569          d.custom_name.set('Python')
     570          d.highlight_target.set('Selected Text')
     571          d.fg_bg_toggle.set(True)
     572  
     573          d.color.set(color)
     574          self.assertEqual(d.style.lookup(d.frame_color_set['style'], 'background'), color)
     575          self.assertEqual(d.highlight_sample.tag_cget('hilite', 'foreground'), color)
     576          self.assertEqual(highpage,
     577                           {'Python': {'hilite-foreground': color}})
     578  
     579      def test_get_new_theme_name(self):
     580          orig_sectionname = configdialog.SectionName
     581          sn = configdialog.SectionName = Func(return_self=True)
     582          d = self.page
     583  
     584          sn.result = 'New Theme'
     585          self.assertEqual(d.get_new_theme_name(''), 'New Theme')
     586  
     587          configdialog.SectionName = orig_sectionname
     588  
     589      def test_save_as_new_theme(self):
     590          d = self.page
     591          gntn = d.get_new_theme_name = Func()
     592          d.theme_source.set(True)
     593  
     594          # No name entered.
     595          gntn.result = ''
     596          d.button_save_custom.invoke()
     597          self.assertNotIn(gntn.result, idleConf.userCfg['highlight'])
     598  
     599          # Name entered.
     600          gntn.result = 'my new theme'
     601          gntn.called = 0
     602          self.assertNotIn(gntn.result, idleConf.userCfg['highlight'])
     603          d.button_save_custom.invoke()
     604          self.assertIn(gntn.result, idleConf.userCfg['highlight'])
     605  
     606          del d.get_new_theme_name
     607  
     608      def test_create_new_and_save_new(self):
     609          eq = self.assertEqual
     610          d = self.page
     611  
     612          # Use default as previously active theme.
     613          d.theme_source.set(True)
     614          d.builtin_name.set('IDLE Classic')
     615          first_new = 'my new custom theme'
     616          second_new = 'my second custom theme'
     617  
     618          # No changes, so themes are an exact copy.
     619          self.assertNotIn(first_new, idleConf.userCfg)
     620          d.create_new(first_new)
     621          eq(idleConf.GetSectionList('user', 'highlight'), [first_new])
     622          eq(idleConf.GetThemeDict('default', 'IDLE Classic'),
     623             idleConf.GetThemeDict('user', first_new))
     624          eq(d.custom_name.get(), first_new)
     625          self.assertFalse(d.theme_source.get())  # Use custom set.
     626          eq(d.set_theme_type.called, 1)
     627  
     628          # Test that changed targets are in new theme.
     629          changes.add_option('highlight', first_new, 'hit-background', 'yellow')
     630          self.assertNotIn(second_new, idleConf.userCfg)
     631          d.create_new(second_new)
     632          eq(idleConf.GetSectionList('user', 'highlight'), [first_new, second_new])
     633          self.assertNotEqual(idleConf.GetThemeDict('user', first_new),
     634                              idleConf.GetThemeDict('user', second_new))
     635          # Check that difference in themes was in `hit-background` from `changes`.
     636          idleConf.SetOption('highlight', first_new, 'hit-background', 'yellow')
     637          eq(idleConf.GetThemeDict('user', first_new),
     638             idleConf.GetThemeDict('user', second_new))
     639  
     640      def test_set_highlight_target(self):
     641          eq = self.assertEqual
     642          d = self.page
     643          del d.set_highlight_target
     644  
     645          # Target is cursor.
     646          d.highlight_target.set('Cursor')
     647          eq(d.fg_on.state(), ('disabled', 'selected'))
     648          eq(d.bg_on.state(), ('disabled',))
     649          self.assertTrue(d.fg_bg_toggle)
     650          eq(d.set_color_sample.called, 1)
     651  
     652          # Target is not cursor.
     653          d.highlight_target.set('Comment')
     654          eq(d.fg_on.state(), ('selected',))
     655          eq(d.bg_on.state(), ())
     656          self.assertTrue(d.fg_bg_toggle)
     657          eq(d.set_color_sample.called, 2)
     658  
     659          d.set_highlight_target = Func()
     660  
     661      def test_set_color_sample_binding(self):
     662          d = self.page
     663          scs = d.set_color_sample
     664  
     665          d.fg_on.invoke()
     666          self.assertEqual(scs.called, 1)
     667  
     668          d.bg_on.invoke()
     669          self.assertEqual(scs.called, 2)
     670  
     671      def test_set_color_sample(self):
     672          d = self.page
     673          del d.set_color_sample
     674          d.highlight_target.set('Selected Text')
     675          d.fg_bg_toggle.set(True)
     676          d.set_color_sample()
     677          self.assertEqual(
     678                  d.style.lookup(d.frame_color_set['style'], 'background'),
     679                  d.highlight_sample.tag_cget('hilite', 'foreground'))
     680          d.set_color_sample = Func()
     681  
     682      def test_paint_theme_sample(self):
     683          eq = self.assertEqual
     684          page = self.page
     685          del page.paint_theme_sample  # Delete masking mock.
     686          hs_tag = page.highlight_sample.tag_cget
     687          gh = idleConf.GetHighlight
     688  
     689          # Create custom theme based on IDLE Dark.
     690          page.theme_source.set(True)
     691          page.builtin_name.set('IDLE Dark')
     692          theme = 'IDLE Test'
     693          page.create_new(theme)
     694          page.set_color_sample.called = 0
     695  
     696          # Base theme with nothing in `changes`.
     697          page.paint_theme_sample()
     698          new_console = {'foreground': 'blue',
     699                         'background': 'yellow',}
     700          for key, value in new_console.items():
     701              self.assertNotEqual(hs_tag('console', key), value)
     702          eq(page.set_color_sample.called, 1)
     703  
     704          # Apply changes.
     705          for key, value in new_console.items():
     706              changes.add_option('highlight', theme, 'console-'+key, value)
     707          page.paint_theme_sample()
     708          for key, value in new_console.items():
     709              eq(hs_tag('console', key), value)
     710          eq(page.set_color_sample.called, 2)
     711  
     712          page.paint_theme_sample = Func()
     713  
     714      def test_delete_custom(self):
     715          eq = self.assertEqual
     716          d = self.page
     717          d.button_delete_custom.state(('!disabled',))
     718          yesno = d.askyesno = Func()
     719          dialog.deactivate_current_config = Func()
     720          dialog.activate_config_changes = Func()
     721  
     722          theme_name = 'spam theme'
     723          idleConf.userCfg['highlight'].SetOption(theme_name, 'name', 'value')
     724          highpage[theme_name] = {'option': 'True'}
     725  
     726          theme_name2 = 'other theme'
     727          idleConf.userCfg['highlight'].SetOption(theme_name2, 'name', 'value')
     728          highpage[theme_name2] = {'option': 'False'}
     729  
     730          # Force custom theme.
     731          d.custom_theme_on.state(('!disabled',))
     732          d.custom_theme_on.invoke()
     733          d.custom_name.set(theme_name)
     734  
     735          # Cancel deletion.
     736          yesno.result = False
     737          d.button_delete_custom.invoke()
     738          eq(yesno.called, 1)
     739          eq(highpage[theme_name], {'option': 'True'})
     740          eq(idleConf.GetSectionList('user', 'highlight'), [theme_name, theme_name2])
     741          eq(dialog.deactivate_current_config.called, 0)
     742          eq(dialog.activate_config_changes.called, 0)
     743          eq(d.set_theme_type.called, 0)
     744  
     745          # Confirm deletion.
     746          yesno.result = True
     747          d.button_delete_custom.invoke()
     748          eq(yesno.called, 2)
     749          self.assertNotIn(theme_name, highpage)
     750          eq(idleConf.GetSectionList('user', 'highlight'), [theme_name2])
     751          eq(d.custom_theme_on.state(), ())
     752          eq(d.custom_name.get(), theme_name2)
     753          eq(dialog.deactivate_current_config.called, 1)
     754          eq(dialog.activate_config_changes.called, 1)
     755          eq(d.set_theme_type.called, 1)
     756  
     757          # Confirm deletion of second theme - empties list.
     758          d.custom_name.set(theme_name2)
     759          yesno.result = True
     760          d.button_delete_custom.invoke()
     761          eq(yesno.called, 3)
     762          self.assertNotIn(theme_name, highpage)
     763          eq(idleConf.GetSectionList('user', 'highlight'), [])
     764          eq(d.custom_theme_on.state(), ('disabled',))
     765          eq(d.custom_name.get(), '- no custom themes -')
     766          eq(dialog.deactivate_current_config.called, 2)
     767          eq(dialog.activate_config_changes.called, 2)
     768          eq(d.set_theme_type.called, 2)
     769  
     770          del dialog.activate_config_changes, dialog.deactivate_current_config
     771          del d.askyesno
     772  
     773  
     774  class ESC[4;38;5;81mKeysPageTest(ESC[4;38;5;149munittestESC[4;38;5;149m.ESC[4;38;5;149mTestCase):
     775      """Test that keys tab widgets enable users to make changes.
     776  
     777      Test that widget actions set vars, that var changes add
     778      options to changes and that key sets works correctly.
     779      """
     780  
     781      @classmethod
     782      def setUpClass(cls):
     783          page = cls.page = dialog.keyspage
     784          dialog.note.select(page)
     785          page.set_keys_type = Func()
     786          page.load_keys_list = Func()
     787  
     788      @classmethod
     789      def tearDownClass(cls):
     790          page = cls.page
     791          del page.set_keys_type, page.load_keys_list
     792  
     793      def setUp(self):
     794          d = self.page
     795          # The following is needed for test_load_key_cfg, _delete_custom_keys.
     796          # This may indicate a defect in some test or function.
     797          for section in idleConf.GetSectionList('user', 'keys'):
     798              idleConf.userCfg['keys'].remove_section(section)
     799          changes.clear()
     800          d.set_keys_type.called = 0
     801          d.load_keys_list.called = 0
     802  
     803      def test_load_key_cfg(self):
     804          tracers.detach()
     805          d = self.page
     806          eq = self.assertEqual
     807  
     808          # Use builtin keyset with no user keysets created.
     809          idleConf.CurrentKeys = mock.Mock(return_value='IDLE Classic OSX')
     810          d.load_key_cfg()
     811          self.assertTrue(d.keyset_source.get())
     812          # builtinlist sets variable builtin_name to the CurrentKeys default.
     813          eq(d.builtin_name.get(), 'IDLE Classic OSX')
     814          eq(d.custom_name.get(), '- no custom keys -')
     815          eq(d.custom_keyset_on.state(), ('disabled',))
     816          eq(d.set_keys_type.called, 1)
     817          eq(d.load_keys_list.called, 1)
     818          eq(d.load_keys_list.args, ('IDLE Classic OSX', ))
     819  
     820          # Builtin keyset with non-empty user keyset list.
     821          idleConf.SetOption('keys', 'test1', 'option', 'value')
     822          idleConf.SetOption('keys', 'test2', 'option2', 'value2')
     823          d.load_key_cfg()
     824          eq(d.builtin_name.get(), 'IDLE Classic OSX')
     825          eq(d.custom_name.get(), 'test1')
     826          eq(d.set_keys_type.called, 2)
     827          eq(d.load_keys_list.called, 2)
     828          eq(d.load_keys_list.args, ('IDLE Classic OSX', ))
     829  
     830          # Use custom keyset.
     831          idleConf.CurrentKeys = mock.Mock(return_value='test2')
     832          idleConf.default_keys = mock.Mock(return_value='IDLE Modern Unix')
     833          idleConf.SetOption('main', 'Keys', 'default', '0')
     834          d.load_key_cfg()
     835          self.assertFalse(d.keyset_source.get())
     836          eq(d.builtin_name.get(), 'IDLE Modern Unix')
     837          eq(d.custom_name.get(), 'test2')
     838          eq(d.set_keys_type.called, 3)
     839          eq(d.load_keys_list.called, 3)
     840          eq(d.load_keys_list.args, ('test2', ))
     841  
     842          del idleConf.CurrentKeys, idleConf.default_keys
     843          tracers.attach()
     844  
     845      def test_keyset_source(self):
     846          eq = self.assertEqual
     847          d = self.page
     848          # Test these separately.
     849          d.var_changed_builtin_name = Func()
     850          d.var_changed_custom_name = Func()
     851          # Builtin selected.
     852          d.builtin_keyset_on.invoke()
     853          eq(mainpage, {'Keys': {'default': 'True'}})
     854          eq(d.var_changed_builtin_name.called, 1)
     855          eq(d.var_changed_custom_name.called, 0)
     856          changes.clear()
     857  
     858          # Custom selected.
     859          d.custom_keyset_on.state(('!disabled',))
     860          d.custom_keyset_on.invoke()
     861          self.assertEqual(mainpage, {'Keys': {'default': 'False'}})
     862          eq(d.var_changed_builtin_name.called, 1)
     863          eq(d.var_changed_custom_name.called, 1)
     864          del d.var_changed_builtin_name, d.var_changed_custom_name
     865  
     866      def test_builtin_name(self):
     867          eq = self.assertEqual
     868          d = self.page
     869          idleConf.userCfg['main'].remove_section('Keys')
     870          item_list = ['IDLE Classic Windows', 'IDLE Classic OSX',
     871                       'IDLE Modern UNIX']
     872  
     873          # Not in old_keys, defaults name to first item.
     874          d.builtinlist.SetMenu(item_list, 'IDLE Modern UNIX')
     875          eq(mainpage, {'Keys': {'name': 'IDLE Classic Windows',
     876                                 'name2': 'IDLE Modern UNIX'}})
     877          eq(d.keys_message['text'], 'New key set, see Help')
     878          eq(d.load_keys_list.called, 1)
     879          eq(d.load_keys_list.args, ('IDLE Modern UNIX', ))
     880  
     881          # Not in old keys - uses name2.
     882          changes.clear()
     883          idleConf.SetOption('main', 'Keys', 'name', 'IDLE Classic Unix')
     884          d.builtinlist.SetMenu(item_list, 'IDLE Modern UNIX')
     885          eq(mainpage, {'Keys': {'name2': 'IDLE Modern UNIX'}})
     886          eq(d.keys_message['text'], 'New key set, see Help')
     887          eq(d.load_keys_list.called, 2)
     888          eq(d.load_keys_list.args, ('IDLE Modern UNIX', ))
     889  
     890          # Builtin name in old_keys.
     891          changes.clear()
     892          d.builtinlist.SetMenu(item_list, 'IDLE Classic OSX')
     893          eq(mainpage, {'Keys': {'name': 'IDLE Classic OSX', 'name2': ''}})
     894          eq(d.keys_message['text'], '')
     895          eq(d.load_keys_list.called, 3)
     896          eq(d.load_keys_list.args, ('IDLE Classic OSX', ))
     897  
     898      def test_custom_name(self):
     899          d = self.page
     900  
     901          # If no selections, doesn't get added.
     902          d.customlist.SetMenu([], '- no custom keys -')
     903          self.assertNotIn('Keys', mainpage)
     904          self.assertEqual(d.load_keys_list.called, 0)
     905  
     906          # Custom name selected.
     907          changes.clear()
     908          d.customlist.SetMenu(['a', 'b', 'c'], 'c')
     909          self.assertEqual(mainpage, {'Keys': {'name': 'c'}})
     910          self.assertEqual(d.load_keys_list.called, 1)
     911  
     912      def test_keybinding(self):
     913          idleConf.SetOption('extensions', 'ZzDummy', 'enable', 'True')
     914          d = self.page
     915          d.custom_name.set('my custom keys')
     916          d.bindingslist.delete(0, 'end')
     917          d.bindingslist.insert(0, 'copy')
     918          d.bindingslist.insert(1, 'z-in')
     919          d.bindingslist.selection_set(0)
     920          d.bindingslist.selection_anchor(0)
     921          # Core binding - adds to keys.
     922          d.keybinding.set('<Key-F11>')
     923          self.assertEqual(keyspage,
     924                           {'my custom keys': {'copy': '<Key-F11>'}})
     925  
     926          # Not a core binding - adds to extensions.
     927          d.bindingslist.selection_set(1)
     928          d.bindingslist.selection_anchor(1)
     929          d.keybinding.set('<Key-F11>')
     930          self.assertEqual(extpage,
     931                           {'ZzDummy_cfgBindings': {'z-in': '<Key-F11>'}})
     932  
     933      def test_set_keys_type(self):
     934          eq = self.assertEqual
     935          d = self.page
     936          del d.set_keys_type
     937  
     938          # Builtin keyset selected.
     939          d.keyset_source.set(True)
     940          d.set_keys_type()
     941          eq(d.builtinlist['state'], NORMAL)
     942          eq(d.customlist['state'], DISABLED)
     943          eq(d.button_delete_custom_keys.state(), ('disabled',))
     944  
     945          # Custom keyset selected.
     946          d.keyset_source.set(False)
     947          d.set_keys_type()
     948          eq(d.builtinlist['state'], DISABLED)
     949          eq(d.custom_keyset_on.state(), ('selected',))
     950          eq(d.customlist['state'], NORMAL)
     951          eq(d.button_delete_custom_keys.state(), ())
     952          d.set_keys_type = Func()
     953  
     954      def test_get_new_keys(self):
     955          eq = self.assertEqual
     956          d = self.page
     957          orig_getkeysdialog = configdialog.GetKeysWindow
     958          gkd = configdialog.GetKeysWindow = Func(return_self=True)
     959          gnkn = d.get_new_keys_name = Func()
     960  
     961          d.button_new_keys.state(('!disabled',))
     962          d.bindingslist.delete(0, 'end')
     963          d.bindingslist.insert(0, 'copy - <Control-Shift-Key-C>')
     964          d.bindingslist.selection_set(0)
     965          d.bindingslist.selection_anchor(0)
     966          d.keybinding.set('Key-a')
     967          d.keyset_source.set(True)  # Default keyset.
     968  
     969          # Default keyset; no change to binding.
     970          gkd.result = ''
     971          d.button_new_keys.invoke()
     972          eq(d.bindingslist.get('anchor'), 'copy - <Control-Shift-Key-C>')
     973          # Keybinding isn't changed when there isn't a change entered.
     974          eq(d.keybinding.get(), 'Key-a')
     975  
     976          # Default keyset; binding changed.
     977          gkd.result = '<Key-F11>'
     978          # No keyset name selected therefore binding not saved.
     979          gnkn.result = ''
     980          d.button_new_keys.invoke()
     981          eq(gnkn.called, 1)
     982          eq(d.bindingslist.get('anchor'), 'copy - <Control-Shift-Key-C>')
     983          # Keyset name selected.
     984          gnkn.result = 'My New Key Set'
     985          d.button_new_keys.invoke()
     986          eq(d.custom_name.get(), gnkn.result)
     987          eq(d.bindingslist.get('anchor'), 'copy - <Key-F11>')
     988          eq(d.keybinding.get(), '<Key-F11>')
     989  
     990          # User keyset; binding changed.
     991          d.keyset_source.set(False)  # Custom keyset.
     992          gnkn.called = 0
     993          gkd.result = '<Key-p>'
     994          d.button_new_keys.invoke()
     995          eq(gnkn.called, 0)
     996          eq(d.bindingslist.get('anchor'), 'copy - <Key-p>')
     997          eq(d.keybinding.get(), '<Key-p>')
     998  
     999          del d.get_new_keys_name
    1000          configdialog.GetKeysWindow = orig_getkeysdialog
    1001  
    1002      def test_get_new_keys_name(self):
    1003          orig_sectionname = configdialog.SectionName
    1004          sn = configdialog.SectionName = Func(return_self=True)
    1005          d = self.page
    1006  
    1007          sn.result = 'New Keys'
    1008          self.assertEqual(d.get_new_keys_name(''), 'New Keys')
    1009  
    1010          configdialog.SectionName = orig_sectionname
    1011  
    1012      def test_save_as_new_key_set(self):
    1013          d = self.page
    1014          gnkn = d.get_new_keys_name = Func()
    1015          d.keyset_source.set(True)
    1016  
    1017          # No name entered.
    1018          gnkn.result = ''
    1019          d.button_save_custom_keys.invoke()
    1020  
    1021          # Name entered.
    1022          gnkn.result = 'my new key set'
    1023          gnkn.called = 0
    1024          self.assertNotIn(gnkn.result, idleConf.userCfg['keys'])
    1025          d.button_save_custom_keys.invoke()
    1026          self.assertIn(gnkn.result, idleConf.userCfg['keys'])
    1027  
    1028          del d.get_new_keys_name
    1029  
    1030      def test_on_bindingslist_select(self):
    1031          d = self.page
    1032          b = d.bindingslist
    1033          b.delete(0, 'end')
    1034          b.insert(0, 'copy')
    1035          b.insert(1, 'find')
    1036          b.activate(0)
    1037  
    1038          b.focus_force()
    1039          b.see(1)
    1040          b.update()
    1041          x, y, dx, dy = b.bbox(1)
    1042          x += dx // 2
    1043          y += dy // 2
    1044          b.event_generate('<Enter>', x=0, y=0)
    1045          b.event_generate('<Motion>', x=x, y=y)
    1046          b.event_generate('<Button-1>', x=x, y=y)
    1047          b.event_generate('<ButtonRelease-1>', x=x, y=y)
    1048          self.assertEqual(b.get('anchor'), 'find')
    1049          self.assertEqual(d.button_new_keys.state(), ())
    1050  
    1051      def test_create_new_key_set_and_save_new_key_set(self):
    1052          eq = self.assertEqual
    1053          d = self.page
    1054  
    1055          # Use default as previously active keyset.
    1056          d.keyset_source.set(True)
    1057          d.builtin_name.set('IDLE Classic Windows')
    1058          first_new = 'my new custom key set'
    1059          second_new = 'my second custom keyset'
    1060  
    1061          # No changes, so keysets are an exact copy.
    1062          self.assertNotIn(first_new, idleConf.userCfg)
    1063          d.create_new_key_set(first_new)
    1064          eq(idleConf.GetSectionList('user', 'keys'), [first_new])
    1065          eq(idleConf.GetKeySet('IDLE Classic Windows'),
    1066             idleConf.GetKeySet(first_new))
    1067          eq(d.custom_name.get(), first_new)
    1068          self.assertFalse(d.keyset_source.get())  # Use custom set.
    1069          eq(d.set_keys_type.called, 1)
    1070  
    1071          # Test that changed keybindings are in new keyset.
    1072          changes.add_option('keys', first_new, 'copy', '<Key-F11>')
    1073          self.assertNotIn(second_new, idleConf.userCfg)
    1074          d.create_new_key_set(second_new)
    1075          eq(idleConf.GetSectionList('user', 'keys'), [first_new, second_new])
    1076          self.assertNotEqual(idleConf.GetKeySet(first_new),
    1077                              idleConf.GetKeySet(second_new))
    1078          # Check that difference in keysets was in option `copy` from `changes`.
    1079          idleConf.SetOption('keys', first_new, 'copy', '<Key-F11>')
    1080          eq(idleConf.GetKeySet(first_new), idleConf.GetKeySet(second_new))
    1081  
    1082      def test_load_keys_list(self):
    1083          eq = self.assertEqual
    1084          d = self.page
    1085          gks = idleConf.GetKeySet = Func()
    1086          del d.load_keys_list
    1087          b = d.bindingslist
    1088  
    1089          b.delete(0, 'end')
    1090          b.insert(0, '<<find>>')
    1091          b.insert(1, '<<help>>')
    1092          gks.result = {'<<copy>>': ['<Control-Key-c>', '<Control-Key-C>'],
    1093                        '<<force-open-completions>>': ['<Control-Key-space>'],
    1094                        '<<spam>>': ['<Key-F11>']}
    1095          changes.add_option('keys', 'my keys', 'spam', '<Shift-Key-a>')
    1096          expected = ('copy - <Control-Key-c> <Control-Key-C>',
    1097                      'force-open-completions - <Control-Key-space>',
    1098                      'spam - <Shift-Key-a>')
    1099  
    1100          # No current selection.
    1101          d.load_keys_list('my keys')
    1102          eq(b.get(0, 'end'), expected)
    1103          eq(b.get('anchor'), '')
    1104          eq(b.curselection(), ())
    1105  
    1106          # Check selection.
    1107          b.selection_set(1)
    1108          b.selection_anchor(1)
    1109          d.load_keys_list('my keys')
    1110          eq(b.get(0, 'end'), expected)
    1111          eq(b.get('anchor'), 'force-open-completions - <Control-Key-space>')
    1112          eq(b.curselection(), (1, ))
    1113  
    1114          # Change selection.
    1115          b.selection_set(2)
    1116          b.selection_anchor(2)
    1117          d.load_keys_list('my keys')
    1118          eq(b.get(0, 'end'), expected)
    1119          eq(b.get('anchor'), 'spam - <Shift-Key-a>')
    1120          eq(b.curselection(), (2, ))
    1121          d.load_keys_list = Func()
    1122  
    1123          del idleConf.GetKeySet
    1124  
    1125      def test_delete_custom_keys(self):
    1126          eq = self.assertEqual
    1127          d = self.page
    1128          d.button_delete_custom_keys.state(('!disabled',))
    1129          yesno = d.askyesno = Func()
    1130          dialog.deactivate_current_config = Func()
    1131          dialog.activate_config_changes = Func()
    1132  
    1133          keyset_name = 'spam key set'
    1134          idleConf.userCfg['keys'].SetOption(keyset_name, 'name', 'value')
    1135          keyspage[keyset_name] = {'option': 'True'}
    1136  
    1137          keyset_name2 = 'other key set'
    1138          idleConf.userCfg['keys'].SetOption(keyset_name2, 'name', 'value')
    1139          keyspage[keyset_name2] = {'option': 'False'}
    1140  
    1141          # Force custom keyset.
    1142          d.custom_keyset_on.state(('!disabled',))
    1143          d.custom_keyset_on.invoke()
    1144          d.custom_name.set(keyset_name)
    1145  
    1146          # Cancel deletion.
    1147          yesno.result = False
    1148          d.button_delete_custom_keys.invoke()
    1149          eq(yesno.called, 1)
    1150          eq(keyspage[keyset_name], {'option': 'True'})
    1151          eq(idleConf.GetSectionList('user', 'keys'), [keyset_name, keyset_name2])
    1152          eq(dialog.deactivate_current_config.called, 0)
    1153          eq(dialog.activate_config_changes.called, 0)
    1154          eq(d.set_keys_type.called, 0)
    1155  
    1156          # Confirm deletion.
    1157          yesno.result = True
    1158          d.button_delete_custom_keys.invoke()
    1159          eq(yesno.called, 2)
    1160          self.assertNotIn(keyset_name, keyspage)
    1161          eq(idleConf.GetSectionList('user', 'keys'), [keyset_name2])
    1162          eq(d.custom_keyset_on.state(), ())
    1163          eq(d.custom_name.get(), keyset_name2)
    1164          eq(dialog.deactivate_current_config.called, 1)
    1165          eq(dialog.activate_config_changes.called, 1)
    1166          eq(d.set_keys_type.called, 1)
    1167  
    1168          # Confirm deletion of second keyset - empties list.
    1169          d.custom_name.set(keyset_name2)
    1170          yesno.result = True
    1171          d.button_delete_custom_keys.invoke()
    1172          eq(yesno.called, 3)
    1173          self.assertNotIn(keyset_name, keyspage)
    1174          eq(idleConf.GetSectionList('user', 'keys'), [])
    1175          eq(d.custom_keyset_on.state(), ('disabled',))
    1176          eq(d.custom_name.get(), '- no custom keys -')
    1177          eq(dialog.deactivate_current_config.called, 2)
    1178          eq(dialog.activate_config_changes.called, 2)
    1179          eq(d.set_keys_type.called, 2)
    1180  
    1181          del dialog.activate_config_changes, dialog.deactivate_current_config
    1182          del d.askyesno
    1183  
    1184  
    1185  class ESC[4;38;5;81mWinPageTest(ESC[4;38;5;149munittestESC[4;38;5;149m.ESC[4;38;5;149mTestCase):
    1186      """Test that general tab widgets enable users to make changes.
    1187  
    1188      Test that widget actions set vars, that var changes add
    1189      options to changes.
    1190      """
    1191      @classmethod
    1192      def setUpClass(cls):
    1193          page = cls.page = dialog.winpage
    1194          dialog.note.select(page)
    1195          page.update()
    1196  
    1197      def setUp(self):
    1198          changes.clear()
    1199  
    1200      def test_load_windows_cfg(self):
    1201          # Set to wrong values, load, check right values.
    1202          eq = self.assertEqual
    1203          d = self.page
    1204          d.startup_edit.set(1)
    1205          d.win_width.set(1)
    1206          d.win_height.set(1)
    1207          d.load_windows_cfg()
    1208          eq(d.startup_edit.get(), 0)
    1209          eq(d.win_width.get(), '80')
    1210          eq(d.win_height.get(), '40')
    1211  
    1212      def test_startup(self):
    1213          d = self.page
    1214          d.startup_editor_on.invoke()
    1215          self.assertEqual(mainpage,
    1216                           {'General': {'editor-on-startup': '1'}})
    1217          changes.clear()
    1218          d.startup_shell_on.invoke()
    1219          self.assertEqual(mainpage,
    1220                           {'General': {'editor-on-startup': '0'}})
    1221  
    1222      def test_editor_size(self):
    1223          d = self.page
    1224          d.win_height_int.delete(0, 'end')
    1225          d.win_height_int.insert(0, '11')
    1226          self.assertEqual(mainpage, {'EditorWindow': {'height': '11'}})
    1227          changes.clear()
    1228          d.win_width_int.delete(0, 'end')
    1229          d.win_width_int.insert(0, '11')
    1230          self.assertEqual(mainpage, {'EditorWindow': {'width': '11'}})
    1231  
    1232      def test_indent_spaces(self):
    1233          d = self.page
    1234          d.indent_chooser.set(6)
    1235          self.assertEqual(d.indent_spaces.get(), '6')
    1236          self.assertEqual(mainpage, {'Indent': {'num-spaces': '6'}})
    1237  
    1238      def test_cursor_blink(self):
    1239          self.page.cursor_blink_bool.invoke()
    1240          self.assertEqual(mainpage, {'EditorWindow': {'cursor-blink': 'False'}})
    1241  
    1242      def test_autocomplete_wait(self):
    1243          self.page.auto_wait_int.delete(0, 'end')
    1244          self.page.auto_wait_int.insert(0, '11')
    1245          self.assertEqual(extpage, {'AutoComplete': {'popupwait': '11'}})
    1246  
    1247      def test_parenmatch(self):
    1248          d = self.page
    1249          eq = self.assertEqual
    1250          d.paren_style_type['menu'].invoke(0)
    1251          eq(extpage, {'ParenMatch': {'style': 'opener'}})
    1252          changes.clear()
    1253          d.paren_flash_time.delete(0, 'end')
    1254          d.paren_flash_time.insert(0, '11')
    1255          eq(extpage, {'ParenMatch': {'flash-delay': '11'}})
    1256          changes.clear()
    1257          d.bell_on.invoke()
    1258          eq(extpage, {'ParenMatch': {'bell': 'False'}})
    1259  
    1260      def test_paragraph(self):
    1261          self.page.format_width_int.delete(0, 'end')
    1262          self.page.format_width_int.insert(0, '11')
    1263          self.assertEqual(extpage, {'FormatParagraph': {'max-width': '11'}})
    1264  
    1265  
    1266  class ESC[4;38;5;81mShedPageTest(ESC[4;38;5;149munittestESC[4;38;5;149m.ESC[4;38;5;149mTestCase):
    1267      """Test that shed tab widgets enable users to make changes.
    1268  
    1269      Test that widget actions set vars, that var changes add
    1270      options to changes.
    1271      """
    1272      @classmethod
    1273      def setUpClass(cls):
    1274          page = cls.page = dialog.shedpage
    1275          dialog.note.select(page)
    1276          page.update()
    1277  
    1278      def setUp(self):
    1279          changes.clear()
    1280  
    1281      def test_load_shelled_cfg(self):
    1282          # Set to wrong values, load, check right values.
    1283          eq = self.assertEqual
    1284          d = self.page
    1285          d.autosave.set(1)
    1286          d.load_shelled_cfg()
    1287          eq(d.autosave.get(), 0)
    1288  
    1289      def test_autosave(self):
    1290          d = self.page
    1291          d.save_auto_on.invoke()
    1292          self.assertEqual(mainpage, {'General': {'autosave': '1'}})
    1293          d.save_ask_on.invoke()
    1294          self.assertEqual(mainpage, {'General': {'autosave': '0'}})
    1295  
    1296      def test_context(self):
    1297          self.page.context_int.delete(0, 'end')
    1298          self.page.context_int.insert(0, '1')
    1299          self.assertEqual(extpage, {'CodeContext': {'maxlines': '1'}})
    1300  
    1301  
    1302  #unittest.skip("Nothing here yet TODO")
    1303  class ESC[4;38;5;81mExtPageTest(ESC[4;38;5;149munittestESC[4;38;5;149m.ESC[4;38;5;149mTestCase):
    1304      """Test that the help source list works correctly."""
    1305      @classmethod
    1306      def setUpClass(cls):
    1307          page = dialog.extpage
    1308          dialog.note.select(page)
    1309  
    1310  
    1311  class ESC[4;38;5;81mHelpSourceTest(ESC[4;38;5;149munittestESC[4;38;5;149m.ESC[4;38;5;149mTestCase):
    1312      """Test that the help source list works correctly."""
    1313      @classmethod
    1314      def setUpClass(cls):
    1315          page = dialog.extpage
    1316          dialog.note.select(page)
    1317          frame = cls.frame = page.frame_help
    1318          frame.set = frame.set_add_delete_state = Func()
    1319          frame.upc = frame.update_help_changes = Func()
    1320          frame.update()
    1321  
    1322      @classmethod
    1323      def tearDownClass(cls):
    1324          frame = cls.frame
    1325          del frame.set, frame.set_add_delete_state
    1326          del frame.upc, frame.update_help_changes
    1327          frame.helplist.delete(0, 'end')
    1328          frame.user_helplist.clear()
    1329  
    1330      def setUp(self):
    1331          changes.clear()
    1332  
    1333      def test_load_helplist(self):
    1334          eq = self.assertEqual
    1335          fr = self.frame
    1336          fr.helplist.insert('end', 'bad')
    1337          fr.user_helplist = ['bad', 'worse']
    1338          idleConf.SetOption('main', 'HelpFiles', '1', 'name;file')
    1339          fr.load_helplist()
    1340          eq(fr.helplist.get(0, 'end'), ('name',))
    1341          eq(fr.user_helplist, [('name', 'file', '1')])
    1342  
    1343      def test_source_selected(self):
    1344          fr = self.frame
    1345          fr.set = fr.set_add_delete_state
    1346          fr.upc = fr.update_help_changes
    1347          helplist = fr.helplist
    1348          dex = 'end'
    1349          helplist.insert(dex, 'source')
    1350          helplist.activate(dex)
    1351  
    1352          helplist.focus_force()
    1353          helplist.see(dex)
    1354          helplist.update()
    1355          x, y, dx, dy = helplist.bbox(dex)
    1356          x += dx // 2
    1357          y += dy // 2
    1358          fr.set.called = fr.upc.called = 0
    1359          helplist.event_generate('<Enter>', x=0, y=0)
    1360          helplist.event_generate('<Motion>', x=x, y=y)
    1361          helplist.event_generate('<Button-1>', x=x, y=y)
    1362          helplist.event_generate('<ButtonRelease-1>', x=x, y=y)
    1363          self.assertEqual(helplist.get('anchor'), 'source')
    1364          self.assertTrue(fr.set.called)
    1365          self.assertFalse(fr.upc.called)
    1366  
    1367      def test_set_add_delete_state(self):
    1368          # Call with 0 items, 1 unselected item, 1 selected item.
    1369          eq = self.assertEqual
    1370          fr = self.frame
    1371          del fr.set_add_delete_state  # Unmask method.
    1372          sad = fr.set_add_delete_state
    1373          h = fr.helplist
    1374  
    1375          h.delete(0, 'end')
    1376          sad()
    1377          eq(fr.button_helplist_edit.state(), ('disabled',))
    1378          eq(fr.button_helplist_remove.state(), ('disabled',))
    1379  
    1380          h.insert(0, 'source')
    1381          sad()
    1382          eq(fr.button_helplist_edit.state(), ('disabled',))
    1383          eq(fr.button_helplist_remove.state(), ('disabled',))
    1384  
    1385          h.selection_set(0)
    1386          sad()
    1387          eq(fr.button_helplist_edit.state(), ())
    1388          eq(fr.button_helplist_remove.state(), ())
    1389          fr.set_add_delete_state = Func()  # Mask method.
    1390  
    1391      def test_helplist_item_add(self):
    1392          # Call without and twice with HelpSource result.
    1393          # Double call enables check on order.
    1394          eq = self.assertEqual
    1395          orig_helpsource = configdialog.HelpSource
    1396          hs = configdialog.HelpSource = Func(return_self=True)
    1397          fr = self.frame
    1398          fr.helplist.delete(0, 'end')
    1399          fr.user_helplist.clear()
    1400          fr.set.called = fr.upc.called = 0
    1401  
    1402          hs.result = ''
    1403          fr.helplist_item_add()
    1404          self.assertTrue(list(fr.helplist.get(0, 'end')) ==
    1405                          fr.user_helplist == [])
    1406          self.assertFalse(fr.upc.called)
    1407  
    1408          hs.result = ('name1', 'file1')
    1409          fr.helplist_item_add()
    1410          hs.result = ('name2', 'file2')
    1411          fr.helplist_item_add()
    1412          eq(fr.helplist.get(0, 'end'), ('name1', 'name2'))
    1413          eq(fr.user_helplist, [('name1', 'file1'), ('name2', 'file2')])
    1414          eq(fr.upc.called, 2)
    1415          self.assertFalse(fr.set.called)
    1416  
    1417          configdialog.HelpSource = orig_helpsource
    1418  
    1419      def test_helplist_item_edit(self):
    1420          # Call without and with HelpSource change.
    1421          eq = self.assertEqual
    1422          orig_helpsource = configdialog.HelpSource
    1423          hs = configdialog.HelpSource = Func(return_self=True)
    1424          fr = self.frame
    1425          fr.helplist.delete(0, 'end')
    1426          fr.helplist.insert(0, 'name1')
    1427          fr.helplist.selection_set(0)
    1428          fr.helplist.selection_anchor(0)
    1429          fr.user_helplist.clear()
    1430          fr.user_helplist.append(('name1', 'file1'))
    1431          fr.set.called = fr.upc.called = 0
    1432  
    1433          hs.result = ''
    1434          fr.helplist_item_edit()
    1435          hs.result = ('name1', 'file1')
    1436          fr.helplist_item_edit()
    1437          eq(fr.helplist.get(0, 'end'), ('name1',))
    1438          eq(fr.user_helplist, [('name1', 'file1')])
    1439          self.assertFalse(fr.upc.called)
    1440  
    1441          hs.result = ('name2', 'file2')
    1442          fr.helplist_item_edit()
    1443          eq(fr.helplist.get(0, 'end'), ('name2',))
    1444          eq(fr.user_helplist, [('name2', 'file2')])
    1445          self.assertTrue(fr.upc.called == fr.set.called == 1)
    1446  
    1447          configdialog.HelpSource = orig_helpsource
    1448  
    1449      def test_helplist_item_remove(self):
    1450          eq = self.assertEqual
    1451          fr = self.frame
    1452          fr.helplist.delete(0, 'end')
    1453          fr.helplist.insert(0, 'name1')
    1454          fr.helplist.selection_set(0)
    1455          fr.helplist.selection_anchor(0)
    1456          fr.user_helplist.clear()
    1457          fr.user_helplist.append(('name1', 'file1'))
    1458          fr.set.called = fr.upc.called = 0
    1459  
    1460          fr.helplist_item_remove()
    1461          eq(fr.helplist.get(0, 'end'), ())
    1462          eq(fr.user_helplist, [])
    1463          self.assertTrue(fr.upc.called == fr.set.called == 1)
    1464  
    1465      def test_update_help_changes(self):
    1466          fr = self.frame
    1467          del fr.update_help_changes
    1468          fr.user_helplist.clear()
    1469          fr.user_helplist.append(('name1', 'file1'))
    1470          fr.user_helplist.append(('name2', 'file2'))
    1471  
    1472          fr.update_help_changes()
    1473          self.assertEqual(mainpage['HelpFiles'],
    1474                           {'1': 'name1;file1', '2': 'name2;file2'})
    1475          fr.update_help_changes = Func()
    1476  
    1477  
    1478  class ESC[4;38;5;81mVarTraceTest(ESC[4;38;5;149munittestESC[4;38;5;149m.ESC[4;38;5;149mTestCase):
    1479  
    1480      @classmethod
    1481      def setUpClass(cls):
    1482          cls.tracers = configdialog.VarTrace()
    1483          cls.iv = IntVar(root)
    1484          cls.bv = BooleanVar(root)
    1485  
    1486      @classmethod
    1487      def tearDownClass(cls):
    1488          del cls.tracers, cls.iv, cls.bv
    1489  
    1490      def setUp(self):
    1491          self.tracers.clear()
    1492          self.called = 0
    1493  
    1494      def var_changed_increment(self, *params):
    1495          self.called += 13
    1496  
    1497      def var_changed_boolean(self, *params):
    1498          pass
    1499  
    1500      def test_init(self):
    1501          tr = self.tracers
    1502          tr.__init__()
    1503          self.assertEqual(tr.untraced, [])
    1504          self.assertEqual(tr.traced, [])
    1505  
    1506      def test_clear(self):
    1507          tr = self.tracers
    1508          tr.untraced.append(0)
    1509          tr.traced.append(1)
    1510          tr.clear()
    1511          self.assertEqual(tr.untraced, [])
    1512          self.assertEqual(tr.traced, [])
    1513  
    1514      def test_add(self):
    1515          tr = self.tracers
    1516          func = Func()
    1517          cb = tr.make_callback = mock.Mock(return_value=func)
    1518  
    1519          iv = tr.add(self.iv, self.var_changed_increment)
    1520          self.assertIs(iv, self.iv)
    1521          bv = tr.add(self.bv, self.var_changed_boolean)
    1522          self.assertIs(bv, self.bv)
    1523  
    1524          sv = StringVar(root)
    1525          sv2 = tr.add(sv, ('main', 'section', 'option'))
    1526          self.assertIs(sv2, sv)
    1527          cb.assert_called_once()
    1528          cb.assert_called_with(sv, ('main', 'section', 'option'))
    1529  
    1530          expected = [(iv, self.var_changed_increment),
    1531                      (bv, self.var_changed_boolean),
    1532                      (sv, func)]
    1533          self.assertEqual(tr.traced, [])
    1534          self.assertEqual(tr.untraced, expected)
    1535  
    1536          del tr.make_callback
    1537  
    1538      def test_make_callback(self):
    1539          cb = self.tracers.make_callback(self.iv, ('main', 'section', 'option'))
    1540          self.assertTrue(callable(cb))
    1541          self.iv.set(42)
    1542          # Not attached, so set didn't invoke the callback.
    1543          self.assertNotIn('section', changes['main'])
    1544          # Invoke callback manually.
    1545          cb()
    1546          self.assertIn('section', changes['main'])
    1547          self.assertEqual(changes['main']['section']['option'], '42')
    1548          changes.clear()
    1549  
    1550      def test_attach_detach(self):
    1551          tr = self.tracers
    1552          iv = tr.add(self.iv, self.var_changed_increment)
    1553          bv = tr.add(self.bv, self.var_changed_boolean)
    1554          expected = [(iv, self.var_changed_increment),
    1555                      (bv, self.var_changed_boolean)]
    1556  
    1557          # Attach callbacks and test call increment.
    1558          tr.attach()
    1559          self.assertEqual(tr.untraced, [])
    1560          self.assertCountEqual(tr.traced, expected)
    1561          iv.set(1)
    1562          self.assertEqual(iv.get(), 1)
    1563          self.assertEqual(self.called, 13)
    1564  
    1565          # Check that only one callback is attached to a variable.
    1566          # If more than one callback were attached, then var_changed_increment
    1567          # would be called twice and the counter would be 2.
    1568          self.called = 0
    1569          tr.attach()
    1570          iv.set(1)
    1571          self.assertEqual(self.called, 13)
    1572  
    1573          # Detach callbacks.
    1574          self.called = 0
    1575          tr.detach()
    1576          self.assertEqual(tr.traced, [])
    1577          self.assertCountEqual(tr.untraced, expected)
    1578          iv.set(1)
    1579          self.assertEqual(self.called, 0)
    1580  
    1581  
    1582  if __name__ == '__main__':
    1583      unittest.main(verbosity=2)