Skip to content

Commit dd023ad

Browse files
csabellaterryjreedy
andcommitted
bpo-30780: Add IDLE configdialog tests (#3592)
Expose dialog buttons to test code and complete their test coverage. Complete test coverage for highlights and keys tabs. Co-authored-by: Terry Jan Reedy <[email protected]>
1 parent 2528a6c commit dd023ad

File tree

4 files changed

+149
-31
lines changed

4 files changed

+149
-31
lines changed

Lib/idlelib/NEWS.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@ Released on 2020-10-05?
33
======================================
44

55

6+
bpo-30780: Add remaining configdialog tests for buttons and
7+
highlights and keys tabs.
8+
69
bpo-39388: Settings dialog Cancel button cancels pending changes.
710

811
bpo-39050: Settings dialog Help button again displays help text.

Lib/idlelib/configdialog.py

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -149,17 +149,19 @@ def create_action_buttons(self):
149149
else:
150150
padding_args = {'padding': (6, 3)}
151151
outer = Frame(self, padding=2)
152-
buttons = Frame(outer, padding=2)
152+
buttons_frame = Frame(outer, padding=2)
153+
self.buttons = {}
153154
for txt, cmd in (
154155
('Ok', self.ok),
155156
('Apply', self.apply),
156157
('Cancel', self.cancel),
157158
('Help', self.help)):
158-
Button(buttons, text=txt, command=cmd, takefocus=FALSE,
159-
**padding_args).pack(side=LEFT, padx=5)
159+
self.buttons[txt] = Button(buttons_frame, text=txt, command=cmd,
160+
takefocus=FALSE, **padding_args)
161+
self.buttons[txt].pack(side=LEFT, padx=5)
160162
# Add space above buttons.
161163
Frame(outer, height=2, borderwidth=0).pack(side=TOP)
162-
buttons.pack(side=BOTTOM)
164+
buttons_frame.pack(side=BOTTOM)
163165
return outer
164166

165167
def ok(self):
@@ -205,7 +207,6 @@ def help(self):
205207
206208
Attributes accessed:
207209
note
208-
209210
Methods:
210211
view_text: Method from textview module.
211212
"""
@@ -852,6 +853,7 @@ def create_page_highlight(self):
852853
text.configure(
853854
font=('courier', 12, ''), cursor='hand2', width=1, height=1,
854855
takefocus=FALSE, highlightthickness=0, wrap=NONE)
856+
# Prevent perhaps invisible selection of word or slice.
855857
text.bind('<Double-Button-1>', lambda e: 'break')
856858
text.bind('<B1-Motion>', lambda e: 'break')
857859
string_tags=(
@@ -1284,8 +1286,7 @@ def save_new(self, theme_name, theme):
12841286
theme_name - string, the name of the new theme
12851287
theme - dictionary containing the new theme
12861288
"""
1287-
if not idleConf.userCfg['highlight'].has_section(theme_name):
1288-
idleConf.userCfg['highlight'].add_section(theme_name)
1289+
idleConf.userCfg['highlight'].AddSection(theme_name)
12891290
for element in theme:
12901291
value = theme[element]
12911292
idleConf.userCfg['highlight'].SetOption(theme_name, element, value)
@@ -1730,8 +1731,7 @@ def save_new_key_set(keyset_name, keyset):
17301731
keyset_name - string, the name of the new key set
17311732
keyset - dictionary containing the new keybindings
17321733
"""
1733-
if not idleConf.userCfg['keys'].has_section(keyset_name):
1734-
idleConf.userCfg['keys'].add_section(keyset_name)
1734+
idleConf.userCfg['keys'].AddSection(keyset_name)
17351735
for event in keyset:
17361736
value = keyset[event]
17371737
idleConf.userCfg['keys'].SetOption(keyset_name, event, value)

Lib/idlelib/idle_test/test_configdialog.py

Lines changed: 136 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
import unittest
99
from unittest import mock
1010
from idlelib.idle_test.mock_idle import Func
11-
from tkinter import Tk, StringVar, IntVar, BooleanVar, DISABLED, NORMAL
11+
from tkinter import (Tk, StringVar, IntVar, BooleanVar, DISABLED, NORMAL)
1212
from idlelib import config
1313
from idlelib.configdialog import idleConf, changes, tracers
1414

@@ -30,13 +30,15 @@
3030
keyspage = changes['keys']
3131
extpage = changes['extensions']
3232

33+
3334
def setUpModule():
3435
global root, dialog
3536
idleConf.userCfg = testcfg
3637
root = Tk()
3738
# root.withdraw() # Comment out, see issue 30870
3839
dialog = configdialog.ConfigDialog(root, 'Test', _utest=True)
3940

41+
4042
def tearDownModule():
4143
global root, dialog
4244
idleConf.userCfg = usercfg
@@ -48,22 +50,56 @@ def tearDownModule():
4850
root = dialog = None
4951

5052

51-
class DialogTest(unittest.TestCase):
53+
class ConfigDialogTest(unittest.TestCase):
54+
55+
def test_deactivate_current_config(self):
56+
pass
57+
58+
def activate_config_changes(self):
59+
pass
60+
5261

53-
@mock.patch(__name__+'.dialog.destroy', new_callable=Func)
54-
def test_cancel(self, destroy):
62+
class ButtonTest(unittest.TestCase):
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.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.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()
5588
changes['main']['something'] = 1
56-
dialog.cancel()
89+
d.buttons['Cancel'].invoke()
5790
self.assertEqual(changes['main'], {})
58-
self.assertEqual(destroy.called, 1)
91+
self.assertEqual(d.destroy.called, 1)
92+
del d.destroy
5993

60-
@mock.patch('idlelib.configdialog.view_text', new_callable=Func)
61-
def test_help(self, view):
94+
def test_click_help(self):
6295
dialog.note.select(dialog.keyspage)
63-
dialog.help()
64-
s = view.kwds['contents']
65-
self.assertTrue(s.startswith('When you click') and
66-
s.endswith('a different name.\n'))
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'))
67103

68104

69105
class FontPageTest(unittest.TestCase):
@@ -438,6 +474,48 @@ def click_it(start):
438474
eq(d.highlight_target.get(), elem[tag])
439475
eq(d.set_highlight_target.called, count)
440476

477+
def test_highlight_sample_double_click(self):
478+
# Test double click on highlight_sample.
479+
eq = self.assertEqual
480+
d = self.page
481+
482+
hs = d.highlight_sample
483+
hs.focus_force()
484+
hs.see(1.0)
485+
hs.update_idletasks()
486+
487+
# Test binding from configdialog.
488+
hs.event_generate('<Enter>', x=0, y=0)
489+
hs.event_generate('<Motion>', x=0, y=0)
490+
# Double click is a sequence of two clicks in a row.
491+
for _ in range(2):
492+
hs.event_generate('<ButtonPress-1>', x=0, y=0)
493+
hs.event_generate('<ButtonRelease-1>', x=0, y=0)
494+
495+
eq(hs.tag_ranges('sel'), ())
496+
497+
def test_highlight_sample_b1_motion(self):
498+
# Test button motion on highlight_sample.
499+
eq = self.assertEqual
500+
d = self.page
501+
502+
hs = d.highlight_sample
503+
hs.focus_force()
504+
hs.see(1.0)
505+
hs.update_idletasks()
506+
507+
x, y, dx, dy, offset = hs.dlineinfo('1.0')
508+
509+
# Test binding from configdialog.
510+
hs.event_generate('<Leave>')
511+
hs.event_generate('<Enter>')
512+
hs.event_generate('<Motion>', x=x, y=y)
513+
hs.event_generate('<ButtonPress-1>', x=x, y=y)
514+
hs.event_generate('<B1-Motion>', x=dx, y=dy)
515+
hs.event_generate('<ButtonRelease-1>', x=dx, y=dy)
516+
517+
eq(hs.tag_ranges('sel'), ())
518+
441519
def test_set_theme_type(self):
442520
eq = self.assertEqual
443521
d = self.page
@@ -666,16 +744,21 @@ def test_delete_custom(self):
666744
idleConf.userCfg['highlight'].SetOption(theme_name, 'name', 'value')
667745
highpage[theme_name] = {'option': 'True'}
668746

747+
theme_name2 = 'other theme'
748+
idleConf.userCfg['highlight'].SetOption(theme_name2, 'name', 'value')
749+
highpage[theme_name2] = {'option': 'False'}
750+
669751
# Force custom theme.
670-
d.theme_source.set(False)
752+
d.custom_theme_on.state(('!disabled',))
753+
d.custom_theme_on.invoke()
671754
d.custom_name.set(theme_name)
672755

673756
# Cancel deletion.
674757
yesno.result = False
675758
d.button_delete_custom.invoke()
676759
eq(yesno.called, 1)
677760
eq(highpage[theme_name], {'option': 'True'})
678-
eq(idleConf.GetSectionList('user', 'highlight'), ['spam theme'])
761+
eq(idleConf.GetSectionList('user', 'highlight'), [theme_name, theme_name2])
679762
eq(dialog.deactivate_current_config.called, 0)
680763
eq(dialog.activate_config_changes.called, 0)
681764
eq(d.set_theme_type.called, 0)
@@ -685,13 +768,26 @@ def test_delete_custom(self):
685768
d.button_delete_custom.invoke()
686769
eq(yesno.called, 2)
687770
self.assertNotIn(theme_name, highpage)
688-
eq(idleConf.GetSectionList('user', 'highlight'), [])
689-
eq(d.custom_theme_on.state(), ('disabled',))
690-
eq(d.custom_name.get(), '- no custom themes -')
771+
eq(idleConf.GetSectionList('user', 'highlight'), [theme_name2])
772+
eq(d.custom_theme_on.state(), ())
773+
eq(d.custom_name.get(), theme_name2)
691774
eq(dialog.deactivate_current_config.called, 1)
692775
eq(dialog.activate_config_changes.called, 1)
693776
eq(d.set_theme_type.called, 1)
694777

778+
# Confirm deletion of second theme - empties list.
779+
d.custom_name.set(theme_name2)
780+
yesno.result = True
781+
d.button_delete_custom.invoke()
782+
eq(yesno.called, 3)
783+
self.assertNotIn(theme_name, highpage)
784+
eq(idleConf.GetSectionList('user', 'highlight'), [])
785+
eq(d.custom_theme_on.state(), ('disabled',))
786+
eq(d.custom_name.get(), '- no custom themes -')
787+
eq(dialog.deactivate_current_config.called, 2)
788+
eq(dialog.activate_config_changes.called, 2)
789+
eq(d.set_theme_type.called, 2)
790+
695791
del dialog.activate_config_changes, dialog.deactivate_current_config
696792
del d.askyesno
697793

@@ -1059,16 +1155,21 @@ def test_delete_custom_keys(self):
10591155
idleConf.userCfg['keys'].SetOption(keyset_name, 'name', 'value')
10601156
keyspage[keyset_name] = {'option': 'True'}
10611157

1158+
keyset_name2 = 'other key set'
1159+
idleConf.userCfg['keys'].SetOption(keyset_name2, 'name', 'value')
1160+
keyspage[keyset_name2] = {'option': 'False'}
1161+
10621162
# Force custom keyset.
1063-
d.keyset_source.set(False)
1163+
d.custom_keyset_on.state(('!disabled',))
1164+
d.custom_keyset_on.invoke()
10641165
d.custom_name.set(keyset_name)
10651166

10661167
# Cancel deletion.
10671168
yesno.result = False
10681169
d.button_delete_custom_keys.invoke()
10691170
eq(yesno.called, 1)
10701171
eq(keyspage[keyset_name], {'option': 'True'})
1071-
eq(idleConf.GetSectionList('user', 'keys'), ['spam key set'])
1172+
eq(idleConf.GetSectionList('user', 'keys'), [keyset_name, keyset_name2])
10721173
eq(dialog.deactivate_current_config.called, 0)
10731174
eq(dialog.activate_config_changes.called, 0)
10741175
eq(d.set_keys_type.called, 0)
@@ -1078,13 +1179,26 @@ def test_delete_custom_keys(self):
10781179
d.button_delete_custom_keys.invoke()
10791180
eq(yesno.called, 2)
10801181
self.assertNotIn(keyset_name, keyspage)
1081-
eq(idleConf.GetSectionList('user', 'keys'), [])
1082-
eq(d.custom_keyset_on.state(), ('disabled',))
1083-
eq(d.custom_name.get(), '- no custom keys -')
1182+
eq(idleConf.GetSectionList('user', 'keys'), [keyset_name2])
1183+
eq(d.custom_keyset_on.state(), ())
1184+
eq(d.custom_name.get(), keyset_name2)
10841185
eq(dialog.deactivate_current_config.called, 1)
10851186
eq(dialog.activate_config_changes.called, 1)
10861187
eq(d.set_keys_type.called, 1)
10871188

1189+
# Confirm deletion of second keyset - empties list.
1190+
d.custom_name.set(keyset_name2)
1191+
yesno.result = True
1192+
d.button_delete_custom_keys.invoke()
1193+
eq(yesno.called, 3)
1194+
self.assertNotIn(keyset_name, keyspage)
1195+
eq(idleConf.GetSectionList('user', 'keys'), [])
1196+
eq(d.custom_keyset_on.state(), ('disabled',))
1197+
eq(d.custom_name.get(), '- no custom keys -')
1198+
eq(dialog.deactivate_current_config.called, 2)
1199+
eq(dialog.activate_config_changes.called, 2)
1200+
eq(d.set_keys_type.called, 2)
1201+
10881202
del dialog.activate_config_changes, dialog.deactivate_current_config
10891203
del d.askyesno
10901204

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Add remaining configdialog tests for buttons and highlights and keys tabs.

0 commit comments

Comments
 (0)