Skip to content

Commit 42abf7f

Browse files
authored
[3.6] bpo-30870: IDLE: Add configdialog fontlist selection unittest (GH-2666) (#2701)
Initial patch by Louie Lu. (cherry picked from commit 9b622fb)
1 parent d8e522f commit 42abf7f

File tree

3 files changed

+108
-29
lines changed

3 files changed

+108
-29
lines changed

Lib/idlelib/configdialog.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,8 @@ def __init__(self, parent, title='', _htest=False, _utest=False):
4747
self.parent = parent
4848
if _htest:
4949
parent.instance_dict = {}
50-
self.withdraw()
50+
if not _utest:
51+
self.withdraw()
5152

5253
self.configure(borderwidth=5)
5354
self.title(title or 'IDLE Preferences')
@@ -76,7 +77,6 @@ def __init__(self, parent, title='', _htest=False, _utest=False):
7677
self.create_widgets()
7778
self.resizable(height=FALSE, width=FALSE)
7879
self.transient(parent)
79-
self.grab_set()
8080
self.protocol("WM_DELETE_WINDOW", self.cancel)
8181
self.fontlist.focus_set()
8282
# XXX Decide whether to keep or delete these key bindings.
@@ -88,6 +88,7 @@ def __init__(self, parent, title='', _htest=False, _utest=False):
8888
self.attach_var_callbacks() # Avoid callbacks during load_configs.
8989

9090
if not _utest:
91+
self.grab_set()
9192
self.wm_deiconify()
9293
self.wait_window()
9394

Lib/idlelib/idle_test/mock_idle.py

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,24 +6,25 @@
66
from idlelib.idle_test.mock_tk import Text
77

88
class Func:
9-
'''Mock function captures args and returns result set by test.
9+
'''Record call, capture args, return/raise result set by test.
1010
11-
Attributes:
12-
self.called - records call even if no args, kwds passed.
13-
self.result - set by init, returned by call.
14-
self.args - captures positional arguments.
15-
self.kwds - captures keyword arguments.
11+
When mock function is called, set or use attributes:
12+
self.called - increment call number even if no args, kwds passed.
13+
self.args - capture positional arguments.
14+
self.kwds - capture keyword arguments.
15+
self.result - return or raise value set in __init__.
1616
17-
Most common use will probably be to mock methods.
17+
Most common use will probably be to mock instance methods.
18+
Given class instance, can set and delete as instance attribute.
1819
Mock_tk.Var and Mbox_func are special variants of this.
1920
'''
2021
def __init__(self, result=None):
21-
self.called = False
22+
self.called = 0
2223
self.result = result
2324
self.args = None
2425
self.kwds = None
2526
def __call__(self, *args, **kwds):
26-
self.called = True
27+
self.called += 1
2728
self.args = args
2829
self.kwds = kwds
2930
if isinstance(self.result, BaseException):

Lib/idlelib/idle_test/test_configdialog.py

Lines changed: 95 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
"""Test idlelib.configdialog.
22
33
Half the class creates dialog, half works with user customizations.
4-
Coverage: 46% just by creating dialog, 56% with current tests.
4+
Coverage: 46% just by creating dialog, 60% with current tests.
55
"""
66
from idlelib.configdialog import ConfigDialog, idleConf, changes
77
from test.support import requires
88
requires('gui')
99
from tkinter import Tk
1010
import unittest
1111
import idlelib.config as config
12+
from idlelib.idle_test.mock_idle import Func
1213

1314
# Tests should not depend on fortuitous user configurations.
1415
# They must not affect actual user .cfg files.
@@ -22,27 +23,29 @@
2223
}
2324

2425
root = None
25-
configure = None
26+
dialog = None
2627
mainpage = changes['main']
2728
highpage = changes['highlight']
2829
keyspage = changes['keys']
2930

30-
class TestDialog(ConfigDialog): pass # Delete?
31+
32+
class TestDialog(ConfigDialog):
33+
pass # Delete?
3134

3235

3336
def setUpModule():
34-
global root, configure
37+
global root, dialog
3538
idleConf.userCfg = testcfg
3639
root = Tk()
37-
root.withdraw()
38-
configure = TestDialog(root, 'Test', _utest=True)
40+
# root.withdraw() # Comment out, see issue 30870
41+
dialog = TestDialog(root, 'Test', _utest=True)
3942

4043

4144
def tearDownModule():
42-
global root, configure
45+
global root, dialog
4346
idleConf.userCfg = usercfg
44-
configure.remove_var_callbacks()
45-
del configure
47+
dialog.remove_var_callbacks()
48+
del dialog
4649
root.update_idletasks()
4750
root.destroy()
4851
del root
@@ -58,31 +61,105 @@ def test_font(self):
5861
default_font = idleConf.GetFont(root, 'main', 'EditorWindow')
5962
default_size = str(default_font[1])
6063
default_bold = default_font[2] == 'bold'
61-
configure.font_name.set('Test Font')
64+
dialog.font_name.set('Test Font')
6265
expected = {'EditorWindow': {'font': 'Test Font',
6366
'font-size': default_size,
6467
'font-bold': str(default_bold)}}
6568
self.assertEqual(mainpage, expected)
6669
changes.clear()
67-
configure.font_size.set(20)
70+
dialog.font_size.set(20)
6871
expected = {'EditorWindow': {'font': 'Test Font',
6972
'font-size': '20',
7073
'font-bold': str(default_bold)}}
7174
self.assertEqual(mainpage, expected)
7275
changes.clear()
73-
configure.font_bold.set(not default_bold)
76+
dialog.font_bold.set(not default_bold)
7477
expected = {'EditorWindow': {'font': 'Test Font',
7578
'font-size': '20',
7679
'font-bold': str(not default_bold)}}
7780
self.assertEqual(mainpage, expected)
7881

79-
#def test_sample(self): pass # TODO
82+
def test_set_sample(self):
83+
# Set_font_sample also sets highlight_sample.
84+
pass
8085

8186
def test_tabspace(self):
82-
configure.space_num.set(6)
87+
dialog.space_num.set(6)
8388
self.assertEqual(mainpage, {'Indent': {'num-spaces': '6'}})
8489

8590

91+
class FontSelectTest(unittest.TestCase):
92+
# These two functions test that selecting a new font in the
93+
# list of fonts changes font_name and calls set_font_sample.
94+
# The fontlist widget and on_fontlist_select event handler
95+
# are tested here together.
96+
97+
@classmethod
98+
def setUpClass(cls):
99+
if dialog.fontlist.size() < 2:
100+
cls.skipTest('need at least 2 fonts')
101+
dialog.set_font_sample = Func() # Mask instance method.
102+
103+
@classmethod
104+
def tearDownClass(cls):
105+
del dialog.set_font_sample # Unmask instance method.
106+
107+
def setUp(self):
108+
dialog.set_font_sample.called = 0
109+
changes.clear()
110+
111+
def test_select_font_key(self):
112+
# Up and Down keys should select a new font.
113+
114+
fontlist = dialog.fontlist
115+
fontlist.activate(0)
116+
font = dialog.fontlist.get('active')
117+
118+
# Test Down key.
119+
fontlist.focus_force()
120+
fontlist.update()
121+
fontlist.event_generate('<Key-Down>')
122+
fontlist.event_generate('<KeyRelease-Down>')
123+
124+
down_font = fontlist.get('active')
125+
self.assertNotEqual(down_font, font)
126+
self.assertIn(dialog.font_name.get(), down_font.lower())
127+
self.assertEqual(dialog.set_font_sample.called, 1)
128+
129+
# Test Up key.
130+
fontlist.focus_force()
131+
fontlist.update()
132+
fontlist.event_generate('<Key-Up>')
133+
fontlist.event_generate('<KeyRelease-Up>')
134+
135+
up_font = fontlist.get('active')
136+
self.assertEqual(up_font, font)
137+
self.assertIn(dialog.font_name.get(), up_font.lower())
138+
self.assertEqual(dialog.set_font_sample.called, 2)
139+
140+
def test_select_font_mouse(self):
141+
# Click on item should select that item.
142+
143+
fontlist = dialog.fontlist
144+
fontlist.activate(0)
145+
146+
# Select next item in listbox
147+
fontlist.focus_force()
148+
fontlist.see(1)
149+
fontlist.update()
150+
x, y, dx, dy = fontlist.bbox(1)
151+
x += dx // 2
152+
y += dy // 2
153+
fontlist.event_generate('<Button-1>', x=x, y=y)
154+
fontlist.event_generate('<ButtonRelease-1>', x=x, y=y)
155+
156+
font1 = fontlist.get(1)
157+
select_font = fontlist.get('anchor')
158+
self.assertEqual(select_font, font1)
159+
self.assertIn(dialog.font_name.get(), font1.lower())
160+
self.assertEqual(dialog.set_font_sample.called, 1)
161+
162+
86163
class HighlightTest(unittest.TestCase):
87164

88165
def setUp(self):
@@ -103,19 +180,19 @@ def setUp(self):
103180
changes.clear()
104181

105182
def test_startup(self):
106-
configure.radio_startup_edit.invoke()
183+
dialog.radio_startup_edit.invoke()
107184
self.assertEqual(mainpage,
108185
{'General': {'editor-on-startup': '1'}})
109186

110187
def test_autosave(self):
111-
configure.radio_save_auto.invoke()
188+
dialog.radio_save_auto.invoke()
112189
self.assertEqual(mainpage, {'General': {'autosave': '1'}})
113190

114191
def test_editor_size(self):
115-
configure.entry_win_height.insert(0, '1')
192+
dialog.entry_win_height.insert(0, '1')
116193
self.assertEqual(mainpage, {'EditorWindow': {'height': '140'}})
117194
changes.clear()
118-
configure.entry_win_width.insert(0, '1')
195+
dialog.entry_win_width.insert(0, '1')
119196
self.assertEqual(mainpage, {'EditorWindow': {'width': '180'}})
120197

121198
#def test_help_sources(self): pass # TODO

0 commit comments

Comments
 (0)