"""Test idlelib.configdialog. Half the class creates dialog, half works with user customizations. Coverage: 63%. """ from idlelib import configdialog from test.support import requires requires('gui') import unittest from unittest import mock from idlelib.idle_test.mock_idle import Func from tkinter import Tk, IntVar, BooleanVar, DISABLED, NORMAL from idlelib import config from idlelib.configdialog import idleConf, changes, tracers # Tests should not depend on fortuitous user configurations. # They must not affect actual user .cfg files. # Use solution from test_config: empty parsers with no filename. usercfg = idleConf.userCfg testcfg = { 'main': config.IdleUserConfParser(''), 'highlight': config.IdleUserConfParser(''), 'keys': config.IdleUserConfParser(''), 'extensions': config.IdleUserConfParser(''), } root = None dialog = None mainpage = changes['main'] highpage = changes['highlight'] keyspage = changes['keys'] def setUpModule(): global root, dialog idleConf.userCfg = testcfg root = Tk() # root.withdraw() # Comment out, see issue 30870 dialog = configdialog.ConfigDialog(root, 'Test', _utest=True) def tearDownModule(): global root, dialog idleConf.userCfg = usercfg tracers.detach() del dialog root.update_idletasks() root.destroy() del root class FontTest(unittest.TestCase): """Test that font widgets enable users to make font changes. Test that widget actions set vars, that var changes add three options to changes and call set_samples, and that set_samples changes the font of both sample boxes. """ @classmethod def setUpClass(cls): dialog.set_samples = Func() # Mask instance method. @classmethod def tearDownClass(cls): del dialog.set_samples # Unmask instance method. def setUp(self): changes.clear() def test_load_font_cfg(self): # Leave widget load test to human visual check. # TODO Improve checks when add IdleConf.get_font_values. d = dialog d.font_name.set('Fake') d.font_size.set('1') d.font_bold.set(True) d.set_samples.called = 0 d.load_font_cfg() self.assertNotEqual(d.font_name.get(), 'Fake') self.assertNotEqual(d.font_size.get(), '1') self.assertFalse(d.font_bold.get()) self.assertEqual(d.set_samples.called, 3) def test_fontlist_key(self): # Up and Down keys should select a new font. if dialog.fontlist.size() < 2: cls.skipTest('need at least 2 fonts') fontlist = dialog.fontlist fontlist.activate(0) font = dialog.fontlist.get('active') # Test Down key. fontlist.focus_force() fontlist.update() fontlist.event_generate('') fontlist.event_generate('') down_font = fontlist.get('active') self.assertNotEqual(down_font, font) self.assertIn(dialog.font_name.get(), down_font.lower()) # Test Up key. fontlist.focus_force() fontlist.update() fontlist.event_generate('') fontlist.event_generate('') up_font = fontlist.get('active') self.assertEqual(up_font, font) self.assertIn(dialog.font_name.get(), up_font.lower()) def test_fontlist_mouse(self): # Click on item should select that item. if dialog.fontlist.size() < 2: cls.skipTest('need at least 2 fonts') fontlist = dialog.fontlist fontlist.activate(0) # Select next item in listbox fontlist.focus_force() fontlist.see(1) fontlist.update() x, y, dx, dy = fontlist.bbox(1) x += dx // 2 y += dy // 2 fontlist.event_generate('', x=x, y=y) fontlist.event_generate('', x=x, y=y) font1 = fontlist.get(1) select_font = fontlist.get('anchor') self.assertEqual(select_font, font1) self.assertIn(dialog.font_name.get(), font1.lower()) def test_sizelist(self): # Click on number shouod select that number d = dialog d.sizelist.variable.set(40) self.assertEqual(d.font_size.get(), '40') def test_bold_toggle(self): # Click on checkbutton should invert it. d = dialog d.font_bold.set(False) d.bold_toggle.invoke() self.assertTrue(d.font_bold.get()) d.bold_toggle.invoke() self.assertFalse(d.font_bold.get()) def test_font_set(self): # Test that setting a font Variable results in 3 provisional # change entries and a call to set_samples. Use values sure to # not be defaults. default_font = idleConf.GetFont(root, 'main', 'EditorWindow') default_size = str(default_font[1]) default_bold = default_font[2] == 'bold' d = dialog d.font_size.set(default_size) d.font_bold.set(default_bold) d.set_samples.called = 0 d.font_name.set('Test Font') expected = {'EditorWindow': {'font': 'Test Font', 'font-size': default_size, 'font-bold': str(default_bold)}} self.assertEqual(mainpage, expected) self.assertEqual(d.set_samples.called, 1) changes.clear() d.font_size.set('20') expected = {'EditorWindow': {'font': 'Test Font', 'font-size': '20', 'font-bold': str(default_bold)}} self.assertEqual(mainpage, expected) self.assertEqual(d.set_samples.called, 2) changes.clear() d.font_bold.set(not default_bold) expected = {'EditorWindow': {'font': 'Test Font', 'font-size': '20', 'font-bold': str(not default_bold)}} self.assertEqual(mainpage, expected) self.assertEqual(d.set_samples.called, 3) def test_set_samples(self): d = dialog del d.set_samples # Unmask method for test d.font_sample, d.highlight_sample = {}, {} d.font_name.set('test') d.font_size.set('5') d.font_bold.set(1) expected = {'font': ('test', '5', 'bold')} # Test set_samples. d.set_samples() self.assertTrue(d.font_sample == d.highlight_sample == expected) del d.font_sample, d.highlight_sample d.set_samples = Func() # Re-mask for other tests. class IndentTest(unittest.TestCase): def test_load_tab_cfg(self): d = dialog d.space_num.set(16) d.load_tab_cfg() self.assertEqual(d.space_num.get(), 4) def test_indent_scale(self): changes.clear() dialog.indent_scale.set(26) self.assertEqual(dialog.space_num.get(), 16) self.assertEqual(mainpage, {'Indent': {'num-spaces': '16'}}) class HighlightTest(unittest.TestCase): def setUp(self): changes.clear() class KeysTest(unittest.TestCase): def setUp(self): changes.clear() class GeneralTest(unittest.TestCase): """Test that general tab widgets enable users to make changes. Test that widget actions set vars, that var changes add options to changes and that helplist works correctly. """ @classmethod def setUpClass(cls): # Mask instance methods used by help functions. d = dialog d.set = d.set_add_delete_state = Func() d.upc = d.update_help_changes = Func() @classmethod def tearDownClass(cls): d = dialog del d.set, d.set_add_delete_state del d.upc, d.update_help_changes d.helplist.delete(0, 'end') d.user_helplist.clear() def setUp(self): changes.clear() def test_load_general_cfg(self): # Set to wrong values, load, check right values. eq = self.assertEqual d = dialog d.startup_edit.set(1) d.autosave.set(1) d.win_width.set(1) d.win_height.set(1) d.helplist.insert('end', 'bad') d.user_helplist = ['bad', 'worse'] idleConf.SetOption('main', 'HelpFiles', '1', 'name;file') d.load_general_cfg() eq(d.startup_edit.get(), 0) eq(d.autosave.get(), 0) eq(d.win_width.get(), '80') eq(d.win_height.get(), '40') eq(d.helplist.get(0, 'end'), ('name',)) eq(d.user_helplist, [('name', 'file', '1')]) def test_startup(self): dialog.startup_editor_on.invoke() self.assertEqual(mainpage, {'General': {'editor-on-startup': '1'}}) changes.clear() dialog.startup_shell_on.invoke() self.assertEqual(mainpage, {'General': {'editor-on-startup': '0'}}) def test_autosave(self): dialog.save_auto_on.invoke() self.assertEqual(mainpage, {'General': {'autosave': '1'}}) dialog.save_ask_on.invoke() self.assertEqual(mainpage, {'General': {'autosave': '0'}}) def test_editor_size(self): dialog.win_height_int.insert(0, '1') self.assertEqual(mainpage, {'EditorWindow': {'height': '140'}}) changes.clear() dialog.win_width_int.insert(0, '1') self.assertEqual(mainpage, {'EditorWindow': {'width': '180'}}) def test_source_selected(self): d = dialog d.set = d.set_add_delete_state d.upc = d.update_help_changes helplist = d.helplist dex = 'end' helplist.insert(dex, 'source') helplist.activate(dex) helplist.focus_force() helplist.see(dex) helplist.update() x, y, dx, dy = helplist.bbox(dex) x += dx // 2 y += dy // 2 d.set.called = d.upc.called = 0 helplist.event_generate('', x=0, y=0) helplist.event_generate('', x=x, y=y) helplist.event_generate('', x=x, y=y) helplist.event_generate('', x=x, y=y) # The following fail after the switch to # self.assertEqual(helplist.get('anchor'), 'source') # self.assertTrue(d.set.called) self.assertFalse(d.upc.called) def test_set_add_delete_state(self): # Call with 0 items, 1 unselected item, 1 selected item. eq = self.assertEqual d = dialog del d.set_add_delete_state # Unmask method. sad = d.set_add_delete_state h = d.helplist h.delete(0, 'end') sad() eq(d.button_helplist_edit['state'], DISABLED) eq(d.button_helplist_remove['state'], DISABLED) h.insert(0, 'source') sad() eq(d.button_helplist_edit['state'], DISABLED) eq(d.button_helplist_remove['state'], DISABLED) h.selection_set(0) sad() eq(d.button_helplist_edit['state'], NORMAL) eq(d.button_helplist_remove['state'], NORMAL) d.set_add_delete_state = Func() # Mask method. def test_helplist_item_add(self): # Call without and twice with HelpSource result. # Double call enables check on order. eq = self.assertEqual orig_helpsource = configdialog.HelpSource hs = configdialog.HelpSource = Func(return_self=True) d = dialog d.helplist.delete(0, 'end') d.user_helplist.clear() d.set.called = d.upc.called = 0 hs.result = '' d.helplist_item_add() self.assertTrue(list(d.helplist.get(0, 'end')) == d.user_helplist == []) self.assertFalse(d.upc.called) hs.result = ('name1', 'file1') d.helplist_item_add() hs.result = ('name2', 'file2') d.helplist_item_add() eq(d.helplist.get(0, 'end'), ('name1', 'name2')) eq(d.user_helplist, [('name1', 'file1'), ('name2', 'file2')]) eq(d.upc.called, 2) self.assertFalse(d.set.called) configdialog.HelpSource = orig_helpsource def test_helplist_item_edit(self): # Call without and with HelpSource change. eq = self.assertEqual orig_helpsource = configdialog.HelpSource hs = configdialog.HelpSource = Func(return_self=True) d = dialog d.helplist.delete(0, 'end') d.helplist.insert(0, 'name1') d.helplist.selection_set(0) d.helplist.selection_anchor(0) d.user_helplist.clear() d.user_helplist.append(('name1', 'file1')) d.set.called = d.upc.called = 0 hs.result = '' d.helplist_item_edit() hs.result = ('name1', 'file1') d.helplist_item_edit() eq(d.helplist.get(0, 'end'), ('name1',)) eq(d.user_helplist, [('name1', 'file1')]) self.assertFalse(d.upc.called) hs.result = ('name2', 'file2') d.helplist_item_edit() eq(d.helplist.get(0, 'end'), ('name2',)) eq(d.user_helplist, [('name2', 'file2')]) self.assertTrue(d.upc.called == d.set.called == 1) configdialog.HelpSource = orig_helpsource def test_helplist_item_remove(self): eq = self.assertEqual d = dialog d.helplist.delete(0, 'end') d.helplist.insert(0, 'name1') d.helplist.selection_set(0) d.helplist.selection_anchor(0) d.user_helplist.clear() d.user_helplist.append(('name1', 'file1')) d.set.called = d.upc.called = 0 d.helplist_item_remove() eq(d.helplist.get(0, 'end'), ()) eq(d.user_helplist, []) self.assertTrue(d.upc.called == d.set.called == 1) def test_update_help_changes(self): d = dialog del d.update_help_changes d.user_helplist.clear() d.user_helplist.append(('name1', 'file1')) d.user_helplist.append(('name2', 'file2')) d.update_help_changes() self.assertEqual(mainpage['HelpFiles'], {'1': 'name1;file1', '2': 'name2;file2'}) d.update_help_changes = Func() class VarTraceTest(unittest.TestCase): def setUp(self): changes.clear() tracers.clear() self.v1 = IntVar(root) self.v2 = BooleanVar(root) self.called = 0 def tearDown(self): del self.v1, self.v2 def var_changed_increment(self, *params): self.called += 13 def var_changed_boolean(self, *params): pass def test_init(self): tracers.__init__() self.assertEqual(tracers.untraced, []) self.assertEqual(tracers.traced, []) def test_clear(self): tracers.untraced.append(0) tracers.traced.append(1) tracers.clear() self.assertEqual(tracers.untraced, []) self.assertEqual(tracers.traced, []) def test_add(self): tr = tracers func = Func() cb = tr.make_callback = mock.Mock(return_value=func) v1 = tr.add(self.v1, self.var_changed_increment) self.assertIsInstance(v1, IntVar) v2 = tr.add(self.v2, self.var_changed_boolean) self.assertIsInstance(v2, BooleanVar) v3 = IntVar(root) v3 = tr.add(v3, ('main', 'section', 'option')) cb.assert_called_once() cb.assert_called_with(v3, ('main', 'section', 'option')) expected = [(v1, self.var_changed_increment), (v2, self.var_changed_boolean), (v3, func)] self.assertEqual(tr.traced, []) self.assertEqual(tr.untraced, expected) del tr.make_callback def test_make_callback(self): cb = tracers.make_callback(self.v1, ('main', 'section', 'option')) self.assertTrue(callable(cb)) self.v1.set(42) # Not attached, so set didn't invoke the callback. self.assertNotIn('section', changes['main']) # Invoke callback manually. cb() self.assertIn('section', changes['main']) self.assertEqual(changes['main']['section']['option'], '42') def test_attach_detach(self): tr = tracers v1 = tr.add(self.v1, self.var_changed_increment) v2 = tr.add(self.v2, self.var_changed_boolean) expected = [(v1, self.var_changed_increment), (v2, self.var_changed_boolean)] # Attach callbacks and test call increment. tr.attach() self.assertEqual(tr.untraced, []) self.assertCountEqual(tr.traced, expected) v1.set(1) self.assertEqual(v1.get(), 1) self.assertEqual(self.called, 13) # Check that only one callback is attached to a variable. # If more than one callback were attached, then var_changed_increment # would be called twice and the counter would be 2. self.called = 0 tr.attach() v1.set(1) self.assertEqual(self.called, 13) # Detach callbacks. self.called = 0 tr.detach() self.assertEqual(tr.traced, []) self.assertCountEqual(tr.untraced, expected) v1.set(1) self.assertEqual(self.called, 0) if __name__ == '__main__': unittest.main(verbosity=2)