summaryrefslogtreecommitdiffstats
path: root/Lib
diff options
context:
space:
mode:
authorTerry Jan Reedy <tjreedy@udel.edu>2016-07-08 04:22:50 (GMT)
committerTerry Jan Reedy <tjreedy@udel.edu>2016-07-08 04:22:50 (GMT)
commit8b22c0aada7ef9fdf6226ee07bd95b4916e5bbb1 (patch)
tree34c1ec8489a871881a08cbabf690de37e7d6e945 /Lib
parentd6402a40d3b52aa945dfc0a875b05510e88a2dab (diff)
downloadcpython-8b22c0aada7ef9fdf6226ee07bd95b4916e5bbb1.zip
cpython-8b22c0aada7ef9fdf6226ee07bd95b4916e5bbb1.tar.gz
cpython-8b22c0aada7ef9fdf6226ee07bd95b4916e5bbb1.tar.bz2
Issue #27380: IDLE: add query.HelpSource class and tests.
Remove modules that are combined in new module.
Diffstat (limited to 'Lib')
-rw-r--r--Lib/idlelib/config_help.py170
-rw-r--r--Lib/idlelib/configdialog.py27
-rw-r--r--Lib/idlelib/idle_test/htest.py30
-rw-r--r--Lib/idlelib/idle_test/test_config_help.py106
-rw-r--r--Lib/idlelib/idle_test/test_query.py214
-rw-r--r--Lib/idlelib/query.py163
6 files changed, 342 insertions, 368 deletions
diff --git a/Lib/idlelib/config_help.py b/Lib/idlelib/config_help.py
deleted file mode 100644
index cde8118..0000000
--- a/Lib/idlelib/config_help.py
+++ /dev/null
@@ -1,170 +0,0 @@
-"Dialog to specify or edit the parameters for a user configured help source."
-
-import os
-import sys
-
-from tkinter import *
-import tkinter.messagebox as tkMessageBox
-import tkinter.filedialog as tkFileDialog
-
-class GetHelpSourceDialog(Toplevel):
- def __init__(self, parent, title, menuItem='', filePath='', _htest=False):
- """Get menu entry and url/ local file location for Additional Help
-
- User selects a name for the Help resource and provides a web url
- or a local file as its source. The user can enter a url or browse
- for the file.
-
- _htest - bool, change box location when running htest
- """
- Toplevel.__init__(self, parent)
- self.configure(borderwidth=5)
- 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.result = None
- self.create_widgets()
- self.menu.set(menuItem)
- self.path.set(filePath)
- self.withdraw() #hide while setting geometry
- #needs to be done here so that the winfo_reqwidth is valid
- self.update_idletasks()
- #centre dialog over parent. below parent if running htest.
- 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)
- if not _htest else 150)))
- self.deiconify() #geometry set, unhide
- self.bind('<Return>', self.ok)
- self.wait_window()
-
- def create_widgets(self):
- self.menu = StringVar(self)
- self.path = StringVar(self)
- self.fontSize = StringVar(self)
- self.frameMain = Frame(self, borderwidth=2, relief=GROOVE)
- self.frameMain.pack(side=TOP, expand=TRUE, fill=BOTH)
- labelMenu = Label(self.frameMain, anchor=W, justify=LEFT,
- text='Menu Item:')
- self.entryMenu = Entry(self.frameMain, textvariable=self.menu,
- width=30)
- self.entryMenu.focus_set()
- labelPath = Label(self.frameMain, anchor=W, justify=LEFT,
- text='Help File Path: Enter URL or browse for file')
- self.entryPath = Entry(self.frameMain, textvariable=self.path,
- width=40)
- self.entryMenu.focus_set()
- labelMenu.pack(anchor=W, padx=5, pady=3)
- self.entryMenu.pack(anchor=W, padx=5, pady=3)
- labelPath.pack(anchor=W, padx=5, pady=3)
- self.entryPath.pack(anchor=W, padx=5, pady=3)
- browseButton = Button(self.frameMain, text='Browse', width=8,
- command=self.browse_file)
- browseButton.pack(pady=3)
- frameButtons = Frame(self)
- frameButtons.pack(side=BOTTOM, fill=X)
- self.buttonOk = Button(frameButtons, text='OK',
- width=8, default=ACTIVE, 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)
-
- def browse_file(self):
- filetypes = [
- ("HTML Files", "*.htm *.html", "TEXT"),
- ("PDF Files", "*.pdf", "TEXT"),
- ("Windows Help Files", "*.chm"),
- ("Text Files", "*.txt", "TEXT"),
- ("All Files", "*")]
- path = self.path.get()
- if path:
- dir, base = os.path.split(path)
- else:
- base = None
- if sys.platform[:3] == 'win':
- dir = os.path.join(os.path.dirname(sys.executable), 'Doc')
- if not os.path.isdir(dir):
- dir = os.getcwd()
- else:
- dir = os.getcwd()
- opendialog = tkFileDialog.Open(parent=self, filetypes=filetypes)
- file = opendialog.show(initialdir=dir, initialfile=base)
- if file:
- self.path.set(file)
-
- def menu_ok(self):
- "Simple validity check for a sensible menu item name"
- menu_ok = True
- menu = self.menu.get()
- menu.strip()
- if not menu:
- tkMessageBox.showerror(title='Menu Item Error',
- message='No menu item specified',
- parent=self)
- self.entryMenu.focus_set()
- menu_ok = False
- elif len(menu) > 30:
- tkMessageBox.showerror(title='Menu Item Error',
- message='Menu item too long:'
- '\nLimit 30 characters.',
- parent=self)
- self.entryMenu.focus_set()
- menu_ok = False
- return menu_ok
-
- def path_ok(self):
- "Simple validity check for menu file path"
- path_ok = True
- path = self.path.get()
- path.strip()
- if not path: #no path specified
- tkMessageBox.showerror(title='File Path Error',
- message='No help file path specified.',
- parent=self)
- self.entryPath.focus_set()
- path_ok = False
- elif path.startswith(('www.', 'http')):
- pass
- else:
- if path[:5] == 'file:':
- path = path[5:]
- if not os.path.exists(path):
- tkMessageBox.showerror(title='File Path Error',
- message='Help file path does not exist.',
- parent=self)
- self.entryPath.focus_set()
- path_ok = False
- return path_ok
-
- def ok(self, event=None):
- if self.menu_ok() and self.path_ok():
- self.result = (self.menu.get().strip(),
- self.path.get().strip())
- if sys.platform == 'darwin':
- path = self.result[1]
- if path.startswith(('www', 'file:', 'http:', 'https:')):
- pass
- else:
- # Mac Safari insists on using the URI form for local files
- self.result = list(self.result)
- self.result[1] = "file://" + path
- self.destroy()
-
- def cancel(self, event=None):
- self.result = None
- self.destroy()
-
-if __name__ == '__main__':
- import unittest
- unittest.main('idlelib.idle_test.test_config_help',
- verbosity=2, exit=False)
-
- from idlelib.idle_test.htest import run
- run(GetHelpSourceDialog)
diff --git a/Lib/idlelib/configdialog.py b/Lib/idlelib/configdialog.py
index 6629d70..388b48f 100644
--- a/Lib/idlelib/configdialog.py
+++ b/Lib/idlelib/configdialog.py
@@ -18,8 +18,7 @@ import tkinter.font as tkFont
from idlelib.config import idleConf
from idlelib.dynoption import DynOptionMenu
from idlelib.config_key import GetKeysDialog
-from idlelib.query import SectionName
-from idlelib.config_help import GetHelpSourceDialog
+from idlelib.query import SectionName, HelpSource
from idlelib.tabbedpages import TabbedPageSet
from idlelib.textview import view_text
from idlelib import macosx
@@ -940,7 +939,8 @@ class ConfigDialog(Toplevel):
self.buttonHelpListRemove.config(state=DISABLED)
def HelpListItemAdd(self):
- helpSource = GetHelpSourceDialog(self, 'New Help Source').result
+ helpSource = HelpSource(self, 'New Help Source',
+ ).result
if helpSource:
self.userHelpList.append((helpSource[0], helpSource[1]))
self.listHelp.insert(END, helpSource[0])
@@ -950,16 +950,17 @@ class ConfigDialog(Toplevel):
def HelpListItemEdit(self):
itemIndex = self.listHelp.index(ANCHOR)
helpSource = self.userHelpList[itemIndex]
- newHelpSource = GetHelpSourceDialog(
- self, 'Edit Help Source', menuItem=helpSource[0],
- filePath=helpSource[1]).result
- if (not newHelpSource) or (newHelpSource == helpSource):
- return #no changes
- self.userHelpList[itemIndex] = newHelpSource
- self.listHelp.delete(itemIndex)
- self.listHelp.insert(itemIndex, newHelpSource[0])
- self.UpdateUserHelpChangedItems()
- self.SetHelpListButtonStates()
+ newHelpSource = HelpSource(
+ self, 'Edit Help Source',
+ menuitem=helpSource[0],
+ filepath=helpSource[1],
+ ).result
+ if newHelpSource and newHelpSource != helpSource:
+ self.userHelpList[itemIndex] = newHelpSource
+ self.listHelp.delete(itemIndex)
+ self.listHelp.insert(itemIndex, newHelpSource[0])
+ self.UpdateUserHelpChangedItems()
+ self.SetHelpListButtonStates()
def HelpListItemRemove(self):
itemIndex = self.listHelp.index(ANCHOR)
diff --git a/Lib/idlelib/idle_test/htest.py b/Lib/idlelib/idle_test/htest.py
index 71302d0..f5311e9 100644
--- a/Lib/idlelib/idle_test/htest.py
+++ b/Lib/idlelib/idle_test/htest.py
@@ -137,18 +137,6 @@ _editor_window_spec = {
"Best to close editor first."
}
-GetHelpSourceDialog_spec = {
- 'file': 'config_help',
- 'kwds': {'title': 'Get helpsource',
- '_htest': True},
- 'msg': "Enter menu item name and help file path\n "
- "<nothing> and more than 30 chars are invalid menu item names.\n"
- "<nothing>, file does not exist are invalid path items.\n"
- "Test for incomplete web address for help file path.\n"
- "A valid entry will be printed to shell with [0k].\n"
- "[Cancel] will print None to shell",
- }
-
# Update once issue21519 is resolved.
GetKeysDialog_spec = {
'file': 'config_key',
@@ -175,6 +163,22 @@ _grep_dialog_spec = {
"should open that file \nin a new EditorWindow."
}
+HelpSource_spec = {
+ 'file': 'query',
+ 'kwds': {'title': 'Help name and source',
+ 'menuitem': 'test',
+ 'filepath': __file__,
+ 'used_names': {'abc'},
+ '_htest': True},
+ 'msg': "Enter menu item name and help file path\n"
+ "'', > than 30 chars, and 'abc' are invalid menu item names.\n"
+ "'' and file does not exist are invalid path items.\n"
+ "Any url ('www...', 'http...') is accepted.\n"
+ "Test Browse with and without path, as cannot unittest.\n"
+ "A valid entry will be printed to shell with [0k]\n"
+ "or <return>. [Cancel] will print None to shell"
+ }
+
_io_binding_spec = {
'file': 'iomenu',
'kwds': {},
@@ -241,7 +245,7 @@ Query_spec = {
'_htest': True},
'msg': "Enter with <Return> or [Ok]. Print valid entry to Shell\n"
"Blank line, after stripping, is ignored\n"
- "Close dialog with valid entry, [Cancel] or [X]",
+ "Close dialog with valid entry, [Cancel] or [X]"
}
diff --git a/Lib/idlelib/idle_test/test_config_help.py b/Lib/idlelib/idle_test/test_config_help.py
deleted file mode 100644
index b89b4e3..0000000
--- a/Lib/idlelib/idle_test/test_config_help.py
+++ /dev/null
@@ -1,106 +0,0 @@
-"""Unittests for idlelib.config_help.py"""
-import unittest
-from idlelib.idle_test.mock_tk import Var, Mbox, Entry
-from idlelib import config_help as help_dialog_module
-
-help_dialog = help_dialog_module.GetHelpSourceDialog
-
-
-class Dummy_help_dialog:
- # Mock for testing the following methods of help_dialog
- menu_ok = help_dialog.menu_ok
- path_ok = help_dialog.path_ok
- ok = help_dialog.ok
- cancel = help_dialog.cancel
- # Attributes, constant or variable, needed for tests
- menu = Var()
- entryMenu = Entry()
- path = Var()
- entryPath = Entry()
- result = None
- destroyed = False
-
- def destroy(self):
- self.destroyed = True
-
-
-# menu_ok and path_ok call Mbox.showerror if menu and path are not ok.
-orig_mbox = help_dialog_module.tkMessageBox
-showerror = Mbox.showerror
-
-
-class ConfigHelpTest(unittest.TestCase):
- dialog = Dummy_help_dialog()
-
- @classmethod
- def setUpClass(cls):
- help_dialog_module.tkMessageBox = Mbox
-
- @classmethod
- def tearDownClass(cls):
- help_dialog_module.tkMessageBox = orig_mbox
-
- def test_blank_menu(self):
- self.dialog.menu.set('')
- self.assertFalse(self.dialog.menu_ok())
- self.assertEqual(showerror.title, 'Menu Item Error')
- self.assertIn('No', showerror.message)
-
- def test_long_menu(self):
- self.dialog.menu.set('hello' * 10)
- self.assertFalse(self.dialog.menu_ok())
- self.assertEqual(showerror.title, 'Menu Item Error')
- self.assertIn('long', showerror.message)
-
- def test_good_menu(self):
- self.dialog.menu.set('help')
- showerror.title = 'No Error' # should not be called
- self.assertTrue(self.dialog.menu_ok())
- self.assertEqual(showerror.title, 'No Error')
-
- def test_blank_path(self):
- self.dialog.path.set('')
- self.assertFalse(self.dialog.path_ok())
- self.assertEqual(showerror.title, 'File Path Error')
- self.assertIn('No', showerror.message)
-
- def test_invalid_file_path(self):
- self.dialog.path.set('foobar' * 100)
- self.assertFalse(self.dialog.path_ok())
- self.assertEqual(showerror.title, 'File Path Error')
- self.assertIn('not exist', showerror.message)
-
- def test_invalid_url_path(self):
- self.dialog.path.set('ww.foobar.com')
- self.assertFalse(self.dialog.path_ok())
- self.assertEqual(showerror.title, 'File Path Error')
- self.assertIn('not exist', showerror.message)
-
- self.dialog.path.set('htt.foobar.com')
- self.assertFalse(self.dialog.path_ok())
- self.assertEqual(showerror.title, 'File Path Error')
- self.assertIn('not exist', showerror.message)
-
- def test_good_path(self):
- self.dialog.path.set('https://docs.python.org')
- showerror.title = 'No Error' # should not be called
- self.assertTrue(self.dialog.path_ok())
- self.assertEqual(showerror.title, 'No Error')
-
- def test_ok(self):
- self.dialog.destroyed = False
- self.dialog.menu.set('help')
- self.dialog.path.set('https://docs.python.org')
- self.dialog.ok()
- self.assertEqual(self.dialog.result, ('help',
- 'https://docs.python.org'))
- self.assertTrue(self.dialog.destroyed)
-
- def test_cancel(self):
- self.dialog.destroyed = False
- self.dialog.cancel()
- self.assertEqual(self.dialog.result, None)
- self.assertTrue(self.dialog.destroyed)
-
-if __name__ == '__main__':
- unittest.main(verbosity=2, exit=False)
diff --git a/Lib/idlelib/idle_test/test_query.py b/Lib/idlelib/idle_test/test_query.py
index 58873c4..45c99fa 100644
--- a/Lib/idlelib/idle_test/test_query.py
+++ b/Lib/idlelib/idle_test/test_query.py
@@ -1,6 +1,16 @@
"""Test idlelib.query.
-Coverage: 100%.
+Non-gui tests for Query, SectionName, ModuleName, and HelpSource use
+dummy versions that extract the non-gui methods and add other needed
+attributes. GUI tests create an instance of each class and simulate
+entries and button clicks. Subclass tests only target the new code in
+the subclass definition.
+
+The appearance of the widgets is checked by the Query and
+HelpSource htests. These are run by running query.py.
+
+Coverage: 94% (100% for Query and SectionName).
+6 of 8 missing are ModuleName exceptions I don't know how to trigger.
"""
from test.support import requires
from tkinter import Tk
@@ -9,21 +19,9 @@ from unittest import mock
from idlelib.idle_test.mock_tk import Var, Mbox_func
from idlelib import query
-Query = query.Query
-class Dummy_Query:
- # Mock for testing the following methods Query
- entry_ok = Query.entry_ok
- ok = Query.ok
- cancel = Query.cancel
- # Attributes, constant or variable, needed for tests
- entry = Var()
- result = None
- destroyed = False
- def destroy(self):
- self.destroyed = True
-
-# entry_ok calls modal messagebox.showerror if entry is not ok.
-# Mock showerrer so don't need to click to continue.
+# Mock entry.showerror messagebox so don't need click to continue
+# when entry_ok and path_ok methods call it to display errors.
+
orig_showerror = query.showerror
showerror = Mbox_func() # Instance has __call__ method.
@@ -34,7 +32,23 @@ def tearDownModule():
query.showerror = orig_showerror
+# NON-GUI TESTS
+
class QueryTest(unittest.TestCase):
+ "Test Query base class."
+
+ class Dummy_Query:
+ # Test the following Query methods.
+ entry_ok = query.Query.entry_ok
+ ok = query.Query.ok
+ cancel = query.Query.cancel
+ # Add attributes needed for the tests.
+ entry = Var()
+ result = None
+ destroyed = False
+ def destroy(self):
+ self.destroyed = True
+
dialog = Dummy_Query()
def setUp(self):
@@ -42,7 +56,7 @@ class QueryTest(unittest.TestCase):
self.dialog.result = None
self.dialog.destroyed = False
- def test_blank_entry(self):
+ def test_entry_ok_blank(self):
dialog = self.dialog
Equal = self.assertEqual
dialog.entry.set(' ')
@@ -51,7 +65,7 @@ class QueryTest(unittest.TestCase):
Equal(showerror.title, 'Entry Error')
self.assertIn('Blank', showerror.message)
- def test_good_entry(self):
+ def test_entry_ok_good(self):
dialog = self.dialog
Equal = self.assertEqual
dialog.entry.set(' good ')
@@ -59,7 +73,17 @@ class QueryTest(unittest.TestCase):
Equal((dialog.result, dialog.destroyed), (None, False))
Equal(showerror.title, None)
- def test_ok(self):
+ def test_ok_blank(self):
+ dialog = self.dialog
+ Equal = self.assertEqual
+ dialog.entry.set('')
+ dialog.entry.focus_set = mock.Mock()
+ Equal(dialog.ok(), None)
+ self.assertTrue(dialog.entry.focus_set.called)
+ del dialog.entry.focus_set
+ Equal((dialog.result, dialog.destroyed), (None, False))
+
+ def test_ok_good(self):
dialog = self.dialog
Equal = self.assertEqual
dialog.entry.set('good')
@@ -73,12 +97,14 @@ class QueryTest(unittest.TestCase):
Equal((dialog.result, dialog.destroyed), (None, True))
-class Dummy_SectionName:
- entry_ok = query.SectionName.entry_ok # Test override.
- used_names = ['used']
- entry = Var()
-
class SectionNameTest(unittest.TestCase):
+ "Test SectionName subclass of Query."
+
+ class Dummy_SectionName:
+ entry_ok = query.SectionName.entry_ok # Function being tested.
+ used_names = ['used']
+ entry = Var()
+
dialog = Dummy_SectionName()
def setUp(self):
@@ -116,12 +142,14 @@ class SectionNameTest(unittest.TestCase):
Equal(showerror.title, None)
-class Dummy_ModuleName:
- entry_ok = query.ModuleName.entry_ok # Test override
- text0 = ''
- entry = Var()
-
class ModuleNameTest(unittest.TestCase):
+ "Test ModuleName subclass of Query."
+
+ class Dummy_ModuleName:
+ entry_ok = query.ModuleName.entry_ok # Funtion being tested.
+ text0 = ''
+ entry = Var()
+
dialog = Dummy_ModuleName()
def setUp(self):
@@ -159,13 +187,119 @@ class ModuleNameTest(unittest.TestCase):
Equal(showerror.title, None)
+# 3 HelpSource test classes each test one function.
+
+orig_platform = query.platform
+
+class HelpsourceBrowsefileTest(unittest.TestCase):
+ "Test browse_file method of ModuleName subclass of Query."
+
+ class Dummy_HelpSource:
+ browse_file = query.HelpSource.browse_file
+ pathvar = Var()
+
+ dialog = Dummy_HelpSource()
+
+ def test_file_replaces_path(self):
+ # Path is widget entry, file is file dialog return.
+ dialog = self.dialog
+ for path, func, result in (
+ # We need all combination to test all (most) code paths.
+ ('', lambda a,b,c:'', ''),
+ ('', lambda a,b,c: __file__, __file__),
+ ('htest', lambda a,b,c:'', 'htest'),
+ ('htest', lambda a,b,c: __file__, __file__)):
+ with self.subTest():
+ dialog.pathvar.set(path)
+ dialog.askfilename = func
+ dialog.browse_file()
+ self.assertEqual(dialog.pathvar.get(), result)
+
+
+class HelpsourcePathokTest(unittest.TestCase):
+ "Test path_ok method of ModuleName subclass of Query."
+
+ class Dummy_HelpSource:
+ path_ok = query.HelpSource.path_ok
+ path = Var()
+
+ dialog = Dummy_HelpSource()
+
+ @classmethod
+ def tearDownClass(cls):
+ query.platform = orig_platform
+
+ def setUp(self):
+ showerror.title = None
+
+ def test_path_ok_blank(self):
+ dialog = self.dialog
+ Equal = self.assertEqual
+ dialog.path.set(' ')
+ Equal(dialog.path_ok(), None)
+ Equal(showerror.title, 'File Path Error')
+ self.assertIn('No help', showerror.message)
+
+ def test_path_ok_bad(self):
+ dialog = self.dialog
+ Equal = self.assertEqual
+ dialog.path.set(__file__ + 'bad-bad-bad')
+ Equal(dialog.path_ok(), None)
+ Equal(showerror.title, 'File Path Error')
+ self.assertIn('not exist', showerror.message)
+
+ def test_path_ok_web(self):
+ dialog = self.dialog
+ Equal = self.assertEqual
+ for url in 'www.py.org', 'http://py.org':
+ with self.subTest():
+ dialog.path.set(url)
+ Equal(dialog.path_ok(), url)
+ Equal(showerror.title, None)
+
+ def test_path_ok_file(self):
+ dialog = self.dialog
+ Equal = self.assertEqual
+ for platform, prefix in ('darwin', 'file://'), ('other', ''):
+ with self.subTest():
+ query.platform = platform
+ dialog.path.set(__file__)
+ Equal(dialog.path_ok(), prefix + __file__)
+ Equal(showerror.title, None)
+
+
+class HelpsourceEntryokTest(unittest.TestCase):
+ "Test entry_ok method of ModuleName subclass of Query."
+
+ class Dummy_HelpSource:
+ entry_ok = query.HelpSource.entry_ok
+ def item_ok(self):
+ return self.name
+ def path_ok(self):
+ return self.path
+
+ dialog = Dummy_HelpSource()
+
+ def test_entry_ok_helpsource(self):
+ dialog = self.dialog
+ for name, path, result in ((None, None, None),
+ (None, 'doc.txt', None),
+ ('doc', None, None),
+ ('doc', 'doc.txt', ('doc', 'doc.txt'))):
+ with self.subTest():
+ dialog.name, dialog.path = name, path
+ self.assertEqual(self.dialog.entry_ok(), result)
+
+
+# GUI TESTS
+
class QueryGuiTest(unittest.TestCase):
@classmethod
def setUpClass(cls):
requires('gui')
cls.root = root = Tk()
- cls.dialog = Query(root, 'TEST', 'test', _utest=True)
+ cls.dialog = query.Query(root, 'TEST', 'test', _utest=True)
cls.dialog.destroy = mock.Mock()
@classmethod
@@ -238,5 +372,25 @@ class ModulenameGuiTest(unittest.TestCase):
del root
+class HelpsourceGuiTest(unittest.TestCase):
+
+ @classmethod
+ def setUpClass(cls):
+ requires('gui')
+
+ def test_click_help_source(self):
+ root = Tk()
+ dialog = query.HelpSource(root, 'T', menuitem='__test__',
+ filepath=__file__, _utest=True)
+ Equal = self.assertEqual
+ Equal(dialog.entry.get(), '__test__')
+ Equal(dialog.path.get(), __file__)
+ dialog.button_ok.invoke()
+ Equal(dialog.result, ('__test__', __file__))
+ del dialog
+ root.destroy()
+ del root
+
+
if __name__ == '__main__':
unittest.main(verbosity=2, exit=False)
diff --git a/Lib/idlelib/query.py b/Lib/idlelib/query.py
index fd9716f..d2d1472 100644
--- a/Lib/idlelib/query.py
+++ b/Lib/idlelib/query.py
@@ -13,10 +13,16 @@ Configdialog uses it for new highlight theme and keybinding set names.
"""
# Query and Section name result from splitting GetCfgSectionNameDialog
# of configSectionNameDialog.py (temporarily config_sec.py) into
-# generic and specific parts.
+# generic and specific parts. 3.6 only, July 2016.
+# ModuleName.entry_ok came from editor.EditorWindow.load_module.
+# HelpSource was extracted from configHelpSourceEdit.py (temporarily
+# config_help.py), with darwin code moved from ok to path_ok.
import importlib
+import os
+from sys import executable, platform # Platform is set for one test.
from tkinter import Toplevel, StringVar
+from tkinter import filedialog
from tkinter.messagebox import showerror
from tkinter.ttk import Frame, Button, Entry, Label
@@ -25,8 +31,8 @@ class Query(Toplevel):
For this base class, accept any non-blank string.
"""
- def __init__(self, parent, title, message, text0='',
- *, _htest=False, _utest=False):
+ def __init__(self, parent, title, message, *, text0='', used_names={},
+ _htest=False, _utest=False):
"""Create popup, do not return until tk widget destroyed.
Additional subclass init must be done before calling this
@@ -35,10 +41,12 @@ class Query(Toplevel):
title - string, title of popup dialog
message - string, informational message to display
text0 - initial value for entry
+ used_names - names already in use
_htest - bool, change box location when running htest
_utest - bool, leave window hidden and not modal
"""
Toplevel.__init__(self, parent)
+ self.withdraw() # Hide while configuring, especially geometry.
self.configure(borderwidth=5)
self.resizable(height=False, width=False)
self.title(title)
@@ -49,27 +57,26 @@ class Query(Toplevel):
self.parent = parent
self.message = message
self.text0 = text0
+ self.used_names = used_names
self.create_widgets()
- self.update_idletasks()
- #needs to be done here so that the winfo_reqwidth is valid
- self.withdraw() # Hide while configuring, especially geometry.
- self.geometry(
+ self.update_idletasks() # Needed here for winfo_reqwidth below.
+ self.geometry( # Center dialog over parent (or below htest box).
"+%d+%d" % (
parent.winfo_rootx() +
(parent.winfo_width()/2 - self.winfo_reqwidth()/2),
parent.winfo_rooty() +
((parent.winfo_height()/2 - self.winfo_reqheight()/2)
if not _htest else 150)
- ) ) #centre dialog over parent (or below htest box)
+ ) )
if not _utest:
- self.deiconify() #geometry set, unhide
+ self.deiconify() # Unhide now that geometry set.
self.wait_window()
def create_widgets(self): # Call from override, if any.
- # Bind widgets needed for entry_ok or unittest to self.
- frame = Frame(self, borderwidth=2, relief='sunken', )
- label = Label(frame, anchor='w', justify='left',
- text=self.message)
+ # Bind to self widgets needed for entry_ok or unittest.
+ self.frame = frame = Frame(self, borderwidth=2, relief='sunken', )
+ entrylabel = Label(frame, anchor='w', justify='left',
+ text=self.message)
self.entryvar = StringVar(self, self.text0)
self.entry = Entry(frame, width=30, textvariable=self.entryvar)
self.entry.focus_set()
@@ -81,7 +88,7 @@ class Query(Toplevel):
width=8, command=self.cancel)
frame.pack(side='top', expand=True, fill='both')
- label.pack(padx=5, pady=5)
+ entrylabel.pack(padx=5, pady=5)
self.entry.pack(padx=5, pady=5)
buttons.pack(side='bottom')
self.button_ok.pack(side='left', padx=5)
@@ -93,7 +100,7 @@ class Query(Toplevel):
if not entry:
showerror(title='Entry Error',
message='Blank line.', parent=self)
- return
+ return None
return entry
def ok(self, event=None): # Do not replace.
@@ -106,7 +113,7 @@ class Query(Toplevel):
self.result = entry
self.destroy()
else:
- # [Ok] (but not <Return>) moves focus. Move it back.
+ # [Ok] moves focus. (<Return> does not.) Move it back.
self.entry.focus_set()
def cancel(self, event=None): # Do not replace.
@@ -117,13 +124,12 @@ class Query(Toplevel):
class SectionName(Query):
"Get a name for a config file section name."
+ # Used in ConfigDialog.GetNewKeysName, .GetNewThemeName (837)
def __init__(self, parent, title, message, used_names,
*, _htest=False, _utest=False):
- "used_names - collection of strings already in use"
- self.used_names = used_names
- Query.__init__(self, parent, title, message,
- _htest=_htest, _utest=_utest)
+ super().__init__(parent, title, message, used_names=used_names,
+ _htest=_htest, _utest=_utest)
def entry_ok(self):
"Return sensible ConfigParser section name or None."
@@ -131,16 +137,16 @@ class SectionName(Query):
if not name:
showerror(title='Name Error',
message='No name specified.', parent=self)
- return
+ return None
elif len(name)>30:
showerror(title='Name Error',
message='Name too long. It should be no more than '+
'30 characters.', parent=self)
- return
+ return None
elif name in self.used_names:
showerror(title='Name Error',
message='This name is already in use.', parent=self)
- return
+ return None
return name
@@ -148,48 +154,133 @@ class ModuleName(Query):
"Get a module name for Open Module menu entry."
# Used in open_module (editor.EditorWindow until move to iobinding).
- def __init__(self, parent, title, message, text0='',
+ def __init__(self, parent, title, message, text0,
*, _htest=False, _utest=False):
- """text0 - name selected in text before Open Module invoked"
- """
- Query.__init__(self, parent, title, message, text0=text0,
- _htest=_htest, _utest=_utest)
+ super().__init__(parent, title, message, text0=text0,
+ _htest=_htest, _utest=_utest)
def entry_ok(self):
"Return entered module name as file path or None."
- # Moved here from Editor_Window.load_module 2016 July.
name = self.entry.get().strip()
if not name:
showerror(title='Name Error',
message='No name specified.', parent=self)
- return
- # XXX Ought to insert current file's directory in front of path
+ return None
+ # XXX Ought to insert current file's directory in front of path.
try:
spec = importlib.util.find_spec(name)
except (ValueError, ImportError) as msg:
showerror("Import Error", str(msg), parent=self)
- return
+ return None
if spec is None:
showerror("Import Error", "module not found",
parent=self)
- return
+ return None
if not isinstance(spec.loader, importlib.abc.SourceLoader):
showerror("Import Error", "not a source-based module",
parent=self)
- return
+ return None
try:
file_path = spec.loader.get_filename(name)
except AttributeError:
showerror("Import Error",
"loader does not support get_filename",
parent=self)
- return
+ return None
return file_path
+class HelpSource(Query):
+ "Get menu name and help source for Help menu."
+ # Used in ConfigDialog.HelpListItemAdd/Edit, (941/9)
+
+ def __init__(self, parent, title, *, menuitem='', filepath='',
+ used_names={}, _htest=False, _utest=False):
+ """Get menu entry and url/local file for Additional Help.
+
+ User enters a name for the Help resource and a web url or file
+ name. The user can browse for the file.
+ """
+ self.filepath = filepath
+ message = 'Name for item on Help menu:'
+ super().__init__(parent, title, message, text0=menuitem,
+ used_names=used_names, _htest=_htest, _utest=_utest)
+
+ def create_widgets(self):
+ super().create_widgets()
+ frame = self.frame
+ pathlabel = Label(frame, anchor='w', justify='left',
+ text='Help File Path: Enter URL or browse for file')
+ self.pathvar = StringVar(self, self.filepath)
+ self.path = Entry(frame, textvariable=self.pathvar, width=40)
+ browse = Button(frame, text='Browse', width=8,
+ command=self.browse_file)
+
+ pathlabel.pack(anchor='w', padx=5, pady=3)
+ self.path.pack(anchor='w', padx=5, pady=3)
+ browse.pack(pady=3)
+
+ def askfilename(self, filetypes, initdir, initfile): # htest #
+ # Extracted from browse_file so can mock for unittests.
+ # Cannot unittest as cannot simulate button clicks.
+ # Test by running htest, such as by running this file.
+ return filedialog.Open(parent=self, filetypes=filetypes)\
+ .show(initialdir=initdir, initialfile=initfile)
+
+ def browse_file(self):
+ filetypes = [
+ ("HTML Files", "*.htm *.html", "TEXT"),
+ ("PDF Files", "*.pdf", "TEXT"),
+ ("Windows Help Files", "*.chm"),
+ ("Text Files", "*.txt", "TEXT"),
+ ("All Files", "*")]
+ path = self.pathvar.get()
+ if path:
+ dir, base = os.path.split(path)
+ else:
+ base = None
+ if platform[:3] == 'win':
+ dir = os.path.join(os.path.dirname(executable), 'Doc')
+ if not os.path.isdir(dir):
+ dir = os.getcwd()
+ else:
+ dir = os.getcwd()
+ file = self.askfilename(filetypes, dir, base)
+ if file:
+ self.pathvar.set(file)
+
+ item_ok = SectionName.entry_ok # localize for test override
+
+ def path_ok(self):
+ "Simple validity check for menu file path"
+ path = self.path.get().strip()
+ if not path: #no path specified
+ showerror(title='File Path Error',
+ message='No help file path specified.',
+ parent=self)
+ return None
+ elif not path.startswith(('www.', 'http')):
+ if path[:5] == 'file:':
+ path = path[5:]
+ if not os.path.exists(path):
+ showerror(title='File Path Error',
+ message='Help file path does not exist.',
+ parent=self)
+ return None
+ if platform == 'darwin': # for Mac Safari
+ path = "file://" + path
+ return path
+
+ def entry_ok(self):
+ "Return apparently valid (name, path) or None"
+ name = self.item_ok()
+ path = self.path_ok()
+ return None if name is None or path is None else (name, path)
+
+
if __name__ == '__main__':
import unittest
unittest.main('idlelib.idle_test.test_query', verbosity=2, exit=False)
from idlelib.idle_test.htest import run
- run(Query)
+ run(Query, HelpSource)