From 9b622fb90331f259894e6edb29b5c64b9366491a Mon Sep 17 00:00:00 2001 From: Louie Lu Date: Fri, 14 Jul 2017 08:35:48 +0800 Subject: bpo-30870: IDLE: Add configdialog fontlist selection unittest (#2666) Initial patch by Louie Lu. --- Lib/idlelib/configdialog.py | 5 +- Lib/idlelib/idle_test/mock_idle.py | 19 ++--- Lib/idlelib/idle_test/test_configdialog.py | 113 ++++++++++++++++++++++++----- 3 files changed, 108 insertions(+), 29 deletions(-) diff --git a/Lib/idlelib/configdialog.py b/Lib/idlelib/configdialog.py index a2cfaab..0d68b80 100644 --- a/Lib/idlelib/configdialog.py +++ b/Lib/idlelib/configdialog.py @@ -47,7 +47,8 @@ class ConfigDialog(Toplevel): self.parent = parent if _htest: parent.instance_dict = {} - self.withdraw() + if not _utest: + self.withdraw() self.configure(borderwidth=5) self.title(title or 'IDLE Preferences') @@ -76,7 +77,6 @@ class ConfigDialog(Toplevel): self.create_widgets() self.resizable(height=FALSE, width=FALSE) self.transient(parent) - self.grab_set() self.protocol("WM_DELETE_WINDOW", self.cancel) self.fontlist.focus_set() # XXX Decide whether to keep or delete these key bindings. @@ -88,6 +88,7 @@ class ConfigDialog(Toplevel): self.attach_var_callbacks() # Avoid callbacks during load_configs. if not _utest: + self.grab_set() self.wm_deiconify() self.wait_window() diff --git a/Lib/idlelib/idle_test/mock_idle.py b/Lib/idlelib/idle_test/mock_idle.py index c7b49ef..8f3147b 100644 --- a/Lib/idlelib/idle_test/mock_idle.py +++ b/Lib/idlelib/idle_test/mock_idle.py @@ -6,24 +6,25 @@ Attributes and methods will be added as needed for tests. from idlelib.idle_test.mock_tk import Text class Func: - '''Mock function captures args and returns result set by test. + '''Record call, capture args, return/raise result set by test. - Attributes: - self.called - records call even if no args, kwds passed. - self.result - set by init, returned by call. - self.args - captures positional arguments. - self.kwds - captures keyword arguments. + When mock function is called, set or use attributes: + self.called - increment call number even if no args, kwds passed. + self.args - capture positional arguments. + self.kwds - capture keyword arguments. + self.result - return or raise value set in __init__. - Most common use will probably be to mock methods. + Most common use will probably be to mock instance methods. + Given class instance, can set and delete as instance attribute. Mock_tk.Var and Mbox_func are special variants of this. ''' def __init__(self, result=None): - self.called = False + self.called = 0 self.result = result self.args = None self.kwds = None def __call__(self, *args, **kwds): - self.called = True + self.called += 1 self.args = args self.kwds = kwds if isinstance(self.result, BaseException): diff --git a/Lib/idlelib/idle_test/test_configdialog.py b/Lib/idlelib/idle_test/test_configdialog.py index 26b045d..4f1f9af 100644 --- a/Lib/idlelib/idle_test/test_configdialog.py +++ b/Lib/idlelib/idle_test/test_configdialog.py @@ -1,7 +1,7 @@ """Test idlelib.configdialog. Half the class creates dialog, half works with user customizations. -Coverage: 46% just by creating dialog, 56% with current tests. +Coverage: 46% just by creating dialog, 60% with current tests. """ from idlelib.configdialog import ConfigDialog, idleConf, changes from test.support import requires @@ -9,6 +9,7 @@ requires('gui') from tkinter import Tk import unittest import idlelib.config as config +from idlelib.idle_test.mock_idle import Func # Tests should not depend on fortuitous user configurations. # They must not affect actual user .cfg files. @@ -22,27 +23,29 @@ testcfg = { } root = None -configure = None +dialog = None mainpage = changes['main'] highpage = changes['highlight'] keyspage = changes['keys'] -class TestDialog(ConfigDialog): pass # Delete? + +class TestDialog(ConfigDialog): + pass # Delete? def setUpModule(): - global root, configure + global root, dialog idleConf.userCfg = testcfg root = Tk() - root.withdraw() - configure = TestDialog(root, 'Test', _utest=True) + # root.withdraw() # Comment out, see issue 30870 + dialog = TestDialog(root, 'Test', _utest=True) def tearDownModule(): - global root, configure + global root, dialog idleConf.userCfg = usercfg - configure.remove_var_callbacks() - del configure + dialog.remove_var_callbacks() + del dialog root.update_idletasks() root.destroy() del root @@ -58,31 +61,105 @@ class FontTabTest(unittest.TestCase): default_font = idleConf.GetFont(root, 'main', 'EditorWindow') default_size = str(default_font[1]) default_bold = default_font[2] == 'bold' - configure.font_name.set('Test Font') + dialog.font_name.set('Test Font') expected = {'EditorWindow': {'font': 'Test Font', 'font-size': default_size, 'font-bold': str(default_bold)}} self.assertEqual(mainpage, expected) changes.clear() - configure.font_size.set(20) + dialog.font_size.set(20) expected = {'EditorWindow': {'font': 'Test Font', 'font-size': '20', 'font-bold': str(default_bold)}} self.assertEqual(mainpage, expected) changes.clear() - configure.font_bold.set(not default_bold) + dialog.font_bold.set(not default_bold) expected = {'EditorWindow': {'font': 'Test Font', 'font-size': '20', 'font-bold': str(not default_bold)}} self.assertEqual(mainpage, expected) - #def test_sample(self): pass # TODO + def test_set_sample(self): + # Set_font_sample also sets highlight_sample. + pass def test_tabspace(self): - configure.space_num.set(6) + dialog.space_num.set(6) self.assertEqual(mainpage, {'Indent': {'num-spaces': '6'}}) +class FontSelectTest(unittest.TestCase): + # These two functions test that selecting a new font in the + # list of fonts changes font_name and calls set_font_sample. + # The fontlist widget and on_fontlist_select event handler + # are tested here together. + + @classmethod + def setUpClass(cls): + if dialog.fontlist.size() < 2: + cls.skipTest('need at least 2 fonts') + dialog.set_font_sample = Func() # Mask instance method. + + @classmethod + def tearDownClass(cls): + del dialog.set_font_sample # Unmask instance method. + + def setUp(self): + dialog.set_font_sample.called = 0 + changes.clear() + + def test_select_font_key(self): + # Up and Down keys should select a new font. + + 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()) + self.assertEqual(dialog.set_font_sample.called, 1) + + # 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()) + self.assertEqual(dialog.set_font_sample.called, 2) + + def test_select_font_mouse(self): + # Click on item should select that item. + + 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()) + self.assertEqual(dialog.set_font_sample.called, 1) + + class HighlightTest(unittest.TestCase): def setUp(self): @@ -103,19 +180,19 @@ class GeneralTest(unittest.TestCase): changes.clear() def test_startup(self): - configure.radio_startup_edit.invoke() + dialog.radio_startup_edit.invoke() self.assertEqual(mainpage, {'General': {'editor-on-startup': '1'}}) def test_autosave(self): - configure.radio_save_auto.invoke() + dialog.radio_save_auto.invoke() self.assertEqual(mainpage, {'General': {'autosave': '1'}}) def test_editor_size(self): - configure.entry_win_height.insert(0, '1') + dialog.entry_win_height.insert(0, '1') self.assertEqual(mainpage, {'EditorWindow': {'height': '140'}}) changes.clear() - configure.entry_win_width.insert(0, '1') + dialog.entry_win_width.insert(0, '1') self.assertEqual(mainpage, {'EditorWindow': {'width': '180'}}) #def test_help_sources(self): pass # TODO -- cgit v0.12