summaryrefslogtreecommitdiffstats
path: root/Lib/idlelib
diff options
context:
space:
mode:
authorTerry Jan Reedy <tjreedy@udel.edu>2013-06-05 18:22:26 (GMT)
committerTerry Jan Reedy <tjreedy@udel.edu>2013-06-05 18:22:26 (GMT)
commit247bd5ea3042caf14ef60b334a2185658bfd1d09 (patch)
treee4dcf2f889f39c72ccb6362bf65c97f259642721 /Lib/idlelib
parenta534fc4b3bd4b731be9304be626bc161f53d7bfb (diff)
downloadcpython-247bd5ea3042caf14ef60b334a2185658bfd1d09.zip
cpython-247bd5ea3042caf14ef60b334a2185658bfd1d09.tar.gz
cpython-247bd5ea3042caf14ef60b334a2185658bfd1d09.tar.bz2
Issue18130: Test class idlelib.configSectionNameDialog.GetCfgSectionNameDialog.
Fix bug in existing human test and add instructions; fix two bugs in tested code; remove redundancies, add spaces, and change two internal method names. Add mock_tk with mocks for tkinter.Variable subclasses and tkinter.messagebox. Use mocks in test_config_name to unittest methods that are otherwise gui-free.
Diffstat (limited to 'Lib/idlelib')
-rw-r--r--Lib/idlelib/configSectionNameDialog.py111
-rw-r--r--Lib/idlelib/idle_test/mock_tk.py63
-rw-r--r--Lib/idlelib/idle_test/test_config_name.py75
3 files changed, 198 insertions, 51 deletions
diff --git a/Lib/idlelib/configSectionNameDialog.py b/Lib/idlelib/configSectionNameDialog.py
index 4378d6f..c5c7f4e 100644
--- a/Lib/idlelib/configSectionNameDialog.py
+++ b/Lib/idlelib/configSectionNameDialog.py
@@ -1,97 +1,106 @@
"""
Dialog that allows user to specify a new config file section name.
Used to get new highlight theme and keybinding set names.
+The 'return value' for the dialog, used two placed in configDialog.py,
+is the .result attribute set in the Ok and Cancel methods.
"""
from tkinter import *
import tkinter.messagebox as tkMessageBox
class GetCfgSectionNameDialog(Toplevel):
- def __init__(self,parent,title,message,usedNames):
+ def __init__(self, parent, title, message, used_names):
"""
message - string, informational message to display
- usedNames - list, list of names already in use for validity check
+ used_names - string collection, names already in use for validity check
"""
Toplevel.__init__(self, parent)
self.configure(borderwidth=5)
- self.resizable(height=FALSE,width=FALSE)
+ self.resizable(height=FALSE, width=FALSE)
self.title(title)
self.transient(parent)
self.grab_set()
self.protocol("WM_DELETE_WINDOW", self.Cancel)
self.parent = parent
- self.message=message
- self.usedNames=usedNames
- self.result=''
- self.CreateWidgets()
- self.withdraw() #hide while setting geometry
+ self.message = message
+ self.used_names = used_names
+ self.create_widgets()
+ self.withdraw() #hide while setting geometry
self.update_idletasks()
#needs to be done here so that the winfo_reqwidth is valid
self.messageInfo.config(width=self.frameMain.winfo_reqwidth())
- self.geometry("+%d+%d" %
- ((parent.winfo_rootx()+((parent.winfo_width()/2)
- -(self.winfo_reqwidth()/2)),
- parent.winfo_rooty()+((parent.winfo_height()/2)
- -(self.winfo_reqheight()/2)) )) ) #centre dialog over parent
- self.deiconify() #geometry set, unhide
+ self.geometry(
+ "+%d+%d" % (
+ parent.winfo_rootx() +
+ (parent.winfo_width()/2 - self.winfo_reqwidth()/2),
+ parent.winfo_rooty() +
+ (parent.winfo_height()/2 - self.winfo_reqheight()/2)
+ ) ) #centre dialog over parent
+ self.deiconify() #geometry set, unhide
self.wait_window()
- def CreateWidgets(self):
- self.name=StringVar(self)
- self.fontSize=StringVar(self)
- self.frameMain = Frame(self,borderwidth=2,relief=SUNKEN)
- self.frameMain.pack(side=TOP,expand=TRUE,fill=BOTH)
- self.messageInfo=Message(self.frameMain,anchor=W,justify=LEFT,padx=5,pady=5,
- text=self.message)#,aspect=200)
- entryName=Entry(self.frameMain,textvariable=self.name,width=30)
+ def create_widgets(self):
+ self.name = StringVar(self.parent)
+ self.fontSize = StringVar(self.parent)
+ self.frameMain = Frame(self, borderwidth=2, relief=SUNKEN)
+ self.frameMain.pack(side=TOP, expand=TRUE, fill=BOTH)
+ self.messageInfo = Message(self.frameMain, anchor=W, justify=LEFT,
+ padx=5, pady=5, text=self.message) #,aspect=200)
+ entryName = Entry(self.frameMain, textvariable=self.name, width=30)
entryName.focus_set()
- self.messageInfo.pack(padx=5,pady=5)#,expand=TRUE,fill=BOTH)
- entryName.pack(padx=5,pady=5)
- frameButtons=Frame(self)
- frameButtons.pack(side=BOTTOM,fill=X)
- self.buttonOk = Button(frameButtons,text='Ok',
- width=8,command=self.Ok)
- self.buttonOk.grid(row=0,column=0,padx=5,pady=5)
- self.buttonCancel = Button(frameButtons,text='Cancel',
- width=8,command=self.Cancel)
- self.buttonCancel.grid(row=0,column=1,padx=5,pady=5)
+ self.messageInfo.pack(padx=5, pady=5) #, expand=TRUE, fill=BOTH)
+ entryName.pack(padx=5, pady=5)
- def NameOk(self):
- #simple validity check for a sensible
- #ConfigParser file section name
- nameOk=1
- name=self.name.get()
- name.strip()
+ frameButtons = Frame(self, pady=2)
+ frameButtons.pack(side=BOTTOM)
+ self.buttonOk = Button(frameButtons, text='Ok',
+ width=8, command=self.Ok)
+ self.buttonOk.pack(side=LEFT, padx=5)
+ self.buttonCancel = Button(frameButtons, text='Cancel',
+ width=8, command=self.Cancel)
+ self.buttonCancel.pack(side=RIGHT, padx=5)
+
+ def name_ok(self):
+ ''' After stripping entered name, check that it is a sensible
+ ConfigParser file section name. Return it if it is, '' if not.
+ '''
+ name = self.name.get().strip()
if not name: #no name specified
tkMessageBox.showerror(title='Name Error',
message='No name specified.', parent=self)
- nameOk=0
elif len(name)>30: #name too long
tkMessageBox.showerror(title='Name Error',
message='Name too long. It should be no more than '+
'30 characters.', parent=self)
- nameOk=0
- elif name in self.usedNames:
+ name = ''
+ elif name in self.used_names:
tkMessageBox.showerror(title='Name Error',
message='This name is already in use.', parent=self)
- nameOk=0
- return nameOk
+ name = ''
+ return name
def Ok(self, event=None):
- if self.NameOk():
- self.result=self.name.get().strip()
+ name = self.name_ok()
+ if name:
+ self.result = name
self.destroy()
def Cancel(self, event=None):
- self.result=''
+ self.result = ''
self.destroy()
if __name__ == '__main__':
- #test the dialog
- root=Tk()
+ import unittest
+ unittest.main('idlelib.idle_test.test_config_name', verbosity=2, exit=False)
+
+ # also human test the dialog
+ root = Tk()
def run():
- keySeq=''
dlg=GetCfgSectionNameDialog(root,'Get Name',
- 'The information here should need to be word wrapped. Test.')
+ "After the text entered with [Ok] is stripped, <nothing>, "
+ "'abc', or more that 30 chars are errors. "
+ "Close with a valid entry (printed), [Cancel], or [X]",
+ {'abc'})
print(dlg.result)
- Button(root,text='Dialog',command=run).pack()
+ Message(root, text='').pack() # will be needed for oher dialog tests
+ Button(root, text='Click to begin dialog test', command=run).pack()
root.mainloop()
diff --git a/Lib/idlelib/idle_test/mock_tk.py b/Lib/idlelib/idle_test/mock_tk.py
new file mode 100644
index 0000000..ef18c33
--- /dev/null
+++ b/Lib/idlelib/idle_test/mock_tk.py
@@ -0,0 +1,63 @@
+"""Classes that replace tkinter gui objects used by an object being tested.
+A gui object is anything with a master or parent paramenter, which is typically
+required in spite of what the doc strings say.
+"""
+
+class Var:
+ "Use for String/Int/BooleanVar: incomplete"
+ def __init__(self, master=None, value=None, name=None):
+ self.master = master
+ self.value = value
+ self.name = name
+ def set(self, value):
+ self.value = value
+ def get(self):
+ return self.value
+
+class Mbox_func:
+ """Generic mock for messagebox functions. All have same call signature.
+ Mbox instantiates once for each function. Tester uses attributes.
+ """
+ def __init__(self):
+ self.result = None # The return for all show funcs
+ def __call__(self, title, message, *args, **kwds):
+ # Save all args for possible examination by tester
+ self.title = title
+ self.message = message
+ self.args = args
+ self.kwds = kwds
+ return self.result # Set by tester for ask functions
+
+class Mbox:
+ """Mock for tkinter.messagebox with an Mbox_func for each function.
+ This module was 'tkMessageBox' in 2.x; hence the 'import as' in 3.x.
+ Example usage in test_module.py for testing functios in module.py:
+ ---
+from idlelib.idle_test.mock_tk import Mbox
+import module
+
+orig_mbox = module.tkMessageBox
+showerror = Mbox.showerror # example, for attribute access in test methods
+
+class Test(unittest.TestCase):
+
+ @classmethod
+ def setUpClass(cls):
+ module.tkMessageBox = Mbox
+
+ @classmethod
+ def tearDownClass(cls):
+ module.tkMessageBox = orig_mbox
+ ---
+ When tkMessageBox functions are the only gui making calls in a method,
+ this replacement makes the method gui-free and unit-testable.
+ For 'ask' functions, set func.result return before calling method.
+ """
+ askokcancel = Mbox_func() # True or False
+ askquestion = Mbox_func() # 'yes' or 'no'
+ askretrycancel = Mbox_func() # True or False
+ askyesno = Mbox_func() # True or False
+ askyesnocancel = Mbox_func() # True, False, or None
+ showerror = Mbox_func() # None
+ showinfo = Mbox_func() # None
+ showwarning = Mbox_func() # None
diff --git a/Lib/idlelib/idle_test/test_config_name.py b/Lib/idlelib/idle_test/test_config_name.py
new file mode 100644
index 0000000..579bf77
--- /dev/null
+++ b/Lib/idlelib/idle_test/test_config_name.py
@@ -0,0 +1,75 @@
+"""Unit tests for idlelib.configSectionNameDialog"""
+import unittest
+from idlelib.idle_test.mock_tk import Var, Mbox
+from idlelib import configSectionNameDialog as name_dialog_module
+
+name_dialog = name_dialog_module.GetCfgSectionNameDialog
+
+class Dummy_name_dialog:
+ # Mock for testing the following methods of name_dialog
+ name_ok = name_dialog.name_ok
+ Ok = name_dialog.Ok
+ Cancel = name_dialog.Cancel
+ # Attributes, constant or variable, needed for tests
+ used_names = ['used']
+ name = Var()
+ result = None
+ destroyed = False
+ def destroy(self):
+ self.destroyed = True
+
+# name_ok calls Mbox.showerror if name is not ok
+orig_mbox = name_dialog_module.tkMessageBox
+showerror = Mbox.showerror
+
+class TestConfigName(unittest.TestCase):
+ dialog = Dummy_name_dialog()
+
+ @classmethod
+ def setUpClass(cls):
+ name_dialog_module.tkMessageBox = Mbox
+
+ @classmethod
+ def tearDownClass(cls):
+ name_dialog_module.tkMessageBox = orig_mbox
+
+ def test_blank_name(self):
+ self.dialog.name.set(' ')
+ self.assertEqual(self.dialog.name_ok(), '')
+ self.assertEqual(showerror.title, 'Name Error')
+ self.assertIn('No', showerror.message)
+
+ def test_used_name(self):
+ self.dialog.name.set('used')
+ self.assertEqual(self.dialog.name_ok(), '')
+ self.assertEqual(showerror.title, 'Name Error')
+ self.assertIn('use', showerror.message)
+
+ def test_long_name(self):
+ self.dialog.name.set('good'*8)
+ self.assertEqual(self.dialog.name_ok(), '')
+ self.assertEqual(showerror.title, 'Name Error')
+ self.assertIn('too long', showerror.message)
+
+ def test_good_name(self):
+ self.dialog.name.set(' good ')
+ showerror.title = 'No Error' # should not be called
+ self.assertEqual(self.dialog.name_ok(), 'good')
+ self.assertEqual(showerror.title, 'No Error')
+
+ def test_ok(self):
+ self.dialog.destroyed = False
+ self.dialog.name.set('good')
+ self.dialog.Ok()
+ self.assertEqual(self.dialog.result, 'good')
+ self.assertTrue(self.dialog.destroyed)
+
+ def test_cancel(self):
+ self.dialog.destroyed = False
+ self.dialog.Cancel()
+ self.assertEqual(self.dialog.result, '')
+ self.assertTrue(self.dialog.destroyed)
+
+
+if __name__ == '__main__':
+ unittest.main(verbosity=2, exit=False)