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 <tjreedy@udel.edu>
This commit is contained in:
Cheryl Sabella 2020-01-27 17:15:56 -05:00 committed by Terry Jan Reedy
parent 2528a6c3d0
commit dd023ad161
4 changed files with 149 additions and 31 deletions

View File

@ -3,6 +3,9 @@ Released on 2020-10-05?
====================================== ======================================
bpo-30780: Add remaining configdialog tests for buttons and
highlights and keys tabs.
bpo-39388: Settings dialog Cancel button cancels pending changes. bpo-39388: Settings dialog Cancel button cancels pending changes.
bpo-39050: Settings dialog Help button again displays help text. bpo-39050: Settings dialog Help button again displays help text.

View File

@ -149,17 +149,19 @@ class ConfigDialog(Toplevel):
else: else:
padding_args = {'padding': (6, 3)} padding_args = {'padding': (6, 3)}
outer = Frame(self, padding=2) outer = Frame(self, padding=2)
buttons = Frame(outer, padding=2) buttons_frame = Frame(outer, padding=2)
self.buttons = {}
for txt, cmd in ( for txt, cmd in (
('Ok', self.ok), ('Ok', self.ok),
('Apply', self.apply), ('Apply', self.apply),
('Cancel', self.cancel), ('Cancel', self.cancel),
('Help', self.help)): ('Help', self.help)):
Button(buttons, text=txt, command=cmd, takefocus=FALSE, self.buttons[txt] = Button(buttons_frame, text=txt, command=cmd,
**padding_args).pack(side=LEFT, padx=5) takefocus=FALSE, **padding_args)
self.buttons[txt].pack(side=LEFT, padx=5)
# Add space above buttons. # Add space above buttons.
Frame(outer, height=2, borderwidth=0).pack(side=TOP) Frame(outer, height=2, borderwidth=0).pack(side=TOP)
buttons.pack(side=BOTTOM) buttons_frame.pack(side=BOTTOM)
return outer return outer
def ok(self): def ok(self):
@ -205,7 +207,6 @@ class ConfigDialog(Toplevel):
Attributes accessed: Attributes accessed:
note note
Methods: Methods:
view_text: Method from textview module. view_text: Method from textview module.
""" """
@ -852,6 +853,7 @@ class HighPage(Frame):
text.configure( text.configure(
font=('courier', 12, ''), cursor='hand2', width=1, height=1, font=('courier', 12, ''), cursor='hand2', width=1, height=1,
takefocus=FALSE, highlightthickness=0, wrap=NONE) takefocus=FALSE, highlightthickness=0, wrap=NONE)
# Prevent perhaps invisible selection of word or slice.
text.bind('<Double-Button-1>', lambda e: 'break') text.bind('<Double-Button-1>', lambda e: 'break')
text.bind('<B1-Motion>', lambda e: 'break') text.bind('<B1-Motion>', lambda e: 'break')
string_tags=( string_tags=(
@ -1284,8 +1286,7 @@ class HighPage(Frame):
theme_name - string, the name of the new theme theme_name - string, the name of the new theme
theme - dictionary containing the new theme theme - dictionary containing the new theme
""" """
if not idleConf.userCfg['highlight'].has_section(theme_name): idleConf.userCfg['highlight'].AddSection(theme_name)
idleConf.userCfg['highlight'].add_section(theme_name)
for element in theme: for element in theme:
value = theme[element] value = theme[element]
idleConf.userCfg['highlight'].SetOption(theme_name, element, value) idleConf.userCfg['highlight'].SetOption(theme_name, element, value)
@ -1730,8 +1731,7 @@ class KeysPage(Frame):
keyset_name - string, the name of the new key set keyset_name - string, the name of the new key set
keyset - dictionary containing the new keybindings keyset - dictionary containing the new keybindings
""" """
if not idleConf.userCfg['keys'].has_section(keyset_name): idleConf.userCfg['keys'].AddSection(keyset_name)
idleConf.userCfg['keys'].add_section(keyset_name)
for event in keyset: for event in keyset:
value = keyset[event] value = keyset[event]
idleConf.userCfg['keys'].SetOption(keyset_name, event, value) idleConf.userCfg['keys'].SetOption(keyset_name, event, value)

View File

@ -8,7 +8,7 @@ requires('gui')
import unittest import unittest
from unittest import mock from unittest import mock
from idlelib.idle_test.mock_idle import Func from idlelib.idle_test.mock_idle import Func
from tkinter import Tk, StringVar, IntVar, BooleanVar, DISABLED, NORMAL from tkinter import (Tk, StringVar, IntVar, BooleanVar, DISABLED, NORMAL)
from idlelib import config from idlelib import config
from idlelib.configdialog import idleConf, changes, tracers from idlelib.configdialog import idleConf, changes, tracers
@ -30,6 +30,7 @@ highpage = changes['highlight']
keyspage = changes['keys'] keyspage = changes['keys']
extpage = changes['extensions'] extpage = changes['extensions']
def setUpModule(): def setUpModule():
global root, dialog global root, dialog
idleConf.userCfg = testcfg idleConf.userCfg = testcfg
@ -37,6 +38,7 @@ def setUpModule():
# root.withdraw() # Comment out, see issue 30870 # root.withdraw() # Comment out, see issue 30870
dialog = configdialog.ConfigDialog(root, 'Test', _utest=True) dialog = configdialog.ConfigDialog(root, 'Test', _utest=True)
def tearDownModule(): def tearDownModule():
global root, dialog global root, dialog
idleConf.userCfg = usercfg idleConf.userCfg = usercfg
@ -48,22 +50,56 @@ def tearDownModule():
root = dialog = None root = dialog = None
class DialogTest(unittest.TestCase): class ConfigDialogTest(unittest.TestCase):
@mock.patch(__name__+'.dialog.destroy', new_callable=Func) def test_deactivate_current_config(self):
def test_cancel(self, destroy): pass
def activate_config_changes(self):
pass
class ButtonTest(unittest.TestCase):
def test_click_ok(self):
d = dialog
apply = d.apply = mock.Mock()
destroy = d.destroy = mock.Mock()
d.buttons['Ok'].invoke()
apply.assert_called_once()
destroy.assert_called_once()
del d.destroy, d.apply
def test_click_apply(self):
d = dialog
deactivate = d.deactivate_current_config = mock.Mock()
save_ext = d.save_all_changed_extensions = mock.Mock()
activate = d.activate_config_changes = mock.Mock()
d.buttons['Apply'].invoke()
deactivate.assert_called_once()
save_ext.assert_called_once()
activate.assert_called_once()
del d.save_all_changed_extensions
del d.activate_config_changes, d.deactivate_current_config
def test_click_cancel(self):
d = dialog
d.destroy = Func()
changes['main']['something'] = 1 changes['main']['something'] = 1
dialog.cancel() d.buttons['Cancel'].invoke()
self.assertEqual(changes['main'], {}) self.assertEqual(changes['main'], {})
self.assertEqual(destroy.called, 1) self.assertEqual(d.destroy.called, 1)
del d.destroy
@mock.patch('idlelib.configdialog.view_text', new_callable=Func) def test_click_help(self):
def test_help(self, view):
dialog.note.select(dialog.keyspage) dialog.note.select(dialog.keyspage)
dialog.help() with mock.patch.object(configdialog, 'view_text',
s = view.kwds['contents'] new_callable=Func) as view:
self.assertTrue(s.startswith('When you click') and dialog.buttons['Help'].invoke()
s.endswith('a different name.\n')) title, contents = view.kwds['title'], view.kwds['contents']
self.assertEqual(title, 'Help for IDLE preferences')
self.assertTrue(contents.startswith('When you click') and
contents.endswith('a different name.\n'))
class FontPageTest(unittest.TestCase): class FontPageTest(unittest.TestCase):
@ -438,6 +474,48 @@ class HighPageTest(unittest.TestCase):
eq(d.highlight_target.get(), elem[tag]) eq(d.highlight_target.get(), elem[tag])
eq(d.set_highlight_target.called, count) eq(d.set_highlight_target.called, count)
def test_highlight_sample_double_click(self):
# Test double click on highlight_sample.
eq = self.assertEqual
d = self.page
hs = d.highlight_sample
hs.focus_force()
hs.see(1.0)
hs.update_idletasks()
# Test binding from configdialog.
hs.event_generate('<Enter>', x=0, y=0)
hs.event_generate('<Motion>', x=0, y=0)
# Double click is a sequence of two clicks in a row.
for _ in range(2):
hs.event_generate('<ButtonPress-1>', x=0, y=0)
hs.event_generate('<ButtonRelease-1>', x=0, y=0)
eq(hs.tag_ranges('sel'), ())
def test_highlight_sample_b1_motion(self):
# Test button motion on highlight_sample.
eq = self.assertEqual
d = self.page
hs = d.highlight_sample
hs.focus_force()
hs.see(1.0)
hs.update_idletasks()
x, y, dx, dy, offset = hs.dlineinfo('1.0')
# Test binding from configdialog.
hs.event_generate('<Leave>')
hs.event_generate('<Enter>')
hs.event_generate('<Motion>', x=x, y=y)
hs.event_generate('<ButtonPress-1>', x=x, y=y)
hs.event_generate('<B1-Motion>', x=dx, y=dy)
hs.event_generate('<ButtonRelease-1>', x=dx, y=dy)
eq(hs.tag_ranges('sel'), ())
def test_set_theme_type(self): def test_set_theme_type(self):
eq = self.assertEqual eq = self.assertEqual
d = self.page d = self.page
@ -666,8 +744,13 @@ class HighPageTest(unittest.TestCase):
idleConf.userCfg['highlight'].SetOption(theme_name, 'name', 'value') idleConf.userCfg['highlight'].SetOption(theme_name, 'name', 'value')
highpage[theme_name] = {'option': 'True'} highpage[theme_name] = {'option': 'True'}
theme_name2 = 'other theme'
idleConf.userCfg['highlight'].SetOption(theme_name2, 'name', 'value')
highpage[theme_name2] = {'option': 'False'}
# Force custom theme. # Force custom theme.
d.theme_source.set(False) d.custom_theme_on.state(('!disabled',))
d.custom_theme_on.invoke()
d.custom_name.set(theme_name) d.custom_name.set(theme_name)
# Cancel deletion. # Cancel deletion.
@ -675,7 +758,7 @@ class HighPageTest(unittest.TestCase):
d.button_delete_custom.invoke() d.button_delete_custom.invoke()
eq(yesno.called, 1) eq(yesno.called, 1)
eq(highpage[theme_name], {'option': 'True'}) eq(highpage[theme_name], {'option': 'True'})
eq(idleConf.GetSectionList('user', 'highlight'), ['spam theme']) eq(idleConf.GetSectionList('user', 'highlight'), [theme_name, theme_name2])
eq(dialog.deactivate_current_config.called, 0) eq(dialog.deactivate_current_config.called, 0)
eq(dialog.activate_config_changes.called, 0) eq(dialog.activate_config_changes.called, 0)
eq(d.set_theme_type.called, 0) eq(d.set_theme_type.called, 0)
@ -685,13 +768,26 @@ class HighPageTest(unittest.TestCase):
d.button_delete_custom.invoke() d.button_delete_custom.invoke()
eq(yesno.called, 2) eq(yesno.called, 2)
self.assertNotIn(theme_name, highpage) self.assertNotIn(theme_name, highpage)
eq(idleConf.GetSectionList('user', 'highlight'), []) eq(idleConf.GetSectionList('user', 'highlight'), [theme_name2])
eq(d.custom_theme_on.state(), ('disabled',)) eq(d.custom_theme_on.state(), ())
eq(d.custom_name.get(), '- no custom themes -') eq(d.custom_name.get(), theme_name2)
eq(dialog.deactivate_current_config.called, 1) eq(dialog.deactivate_current_config.called, 1)
eq(dialog.activate_config_changes.called, 1) eq(dialog.activate_config_changes.called, 1)
eq(d.set_theme_type.called, 1) eq(d.set_theme_type.called, 1)
# Confirm deletion of second theme - empties list.
d.custom_name.set(theme_name2)
yesno.result = True
d.button_delete_custom.invoke()
eq(yesno.called, 3)
self.assertNotIn(theme_name, highpage)
eq(idleConf.GetSectionList('user', 'highlight'), [])
eq(d.custom_theme_on.state(), ('disabled',))
eq(d.custom_name.get(), '- no custom themes -')
eq(dialog.deactivate_current_config.called, 2)
eq(dialog.activate_config_changes.called, 2)
eq(d.set_theme_type.called, 2)
del dialog.activate_config_changes, dialog.deactivate_current_config del dialog.activate_config_changes, dialog.deactivate_current_config
del d.askyesno del d.askyesno
@ -1059,8 +1155,13 @@ class KeysPageTest(unittest.TestCase):
idleConf.userCfg['keys'].SetOption(keyset_name, 'name', 'value') idleConf.userCfg['keys'].SetOption(keyset_name, 'name', 'value')
keyspage[keyset_name] = {'option': 'True'} keyspage[keyset_name] = {'option': 'True'}
keyset_name2 = 'other key set'
idleConf.userCfg['keys'].SetOption(keyset_name2, 'name', 'value')
keyspage[keyset_name2] = {'option': 'False'}
# Force custom keyset. # Force custom keyset.
d.keyset_source.set(False) d.custom_keyset_on.state(('!disabled',))
d.custom_keyset_on.invoke()
d.custom_name.set(keyset_name) d.custom_name.set(keyset_name)
# Cancel deletion. # Cancel deletion.
@ -1068,7 +1169,7 @@ class KeysPageTest(unittest.TestCase):
d.button_delete_custom_keys.invoke() d.button_delete_custom_keys.invoke()
eq(yesno.called, 1) eq(yesno.called, 1)
eq(keyspage[keyset_name], {'option': 'True'}) eq(keyspage[keyset_name], {'option': 'True'})
eq(idleConf.GetSectionList('user', 'keys'), ['spam key set']) eq(idleConf.GetSectionList('user', 'keys'), [keyset_name, keyset_name2])
eq(dialog.deactivate_current_config.called, 0) eq(dialog.deactivate_current_config.called, 0)
eq(dialog.activate_config_changes.called, 0) eq(dialog.activate_config_changes.called, 0)
eq(d.set_keys_type.called, 0) eq(d.set_keys_type.called, 0)
@ -1078,13 +1179,26 @@ class KeysPageTest(unittest.TestCase):
d.button_delete_custom_keys.invoke() d.button_delete_custom_keys.invoke()
eq(yesno.called, 2) eq(yesno.called, 2)
self.assertNotIn(keyset_name, keyspage) self.assertNotIn(keyset_name, keyspage)
eq(idleConf.GetSectionList('user', 'keys'), []) eq(idleConf.GetSectionList('user', 'keys'), [keyset_name2])
eq(d.custom_keyset_on.state(), ('disabled',)) eq(d.custom_keyset_on.state(), ())
eq(d.custom_name.get(), '- no custom keys -') eq(d.custom_name.get(), keyset_name2)
eq(dialog.deactivate_current_config.called, 1) eq(dialog.deactivate_current_config.called, 1)
eq(dialog.activate_config_changes.called, 1) eq(dialog.activate_config_changes.called, 1)
eq(d.set_keys_type.called, 1) eq(d.set_keys_type.called, 1)
# Confirm deletion of second keyset - empties list.
d.custom_name.set(keyset_name2)
yesno.result = True
d.button_delete_custom_keys.invoke()
eq(yesno.called, 3)
self.assertNotIn(keyset_name, keyspage)
eq(idleConf.GetSectionList('user', 'keys'), [])
eq(d.custom_keyset_on.state(), ('disabled',))
eq(d.custom_name.get(), '- no custom keys -')
eq(dialog.deactivate_current_config.called, 2)
eq(dialog.activate_config_changes.called, 2)
eq(d.set_keys_type.called, 2)
del dialog.activate_config_changes, dialog.deactivate_current_config del dialog.activate_config_changes, dialog.deactivate_current_config
del d.askyesno del d.askyesno

View File

@ -0,0 +1 @@
Add remaining configdialog tests for buttons and highlights and keys tabs.