diff -r adbeef96ae95 Lib/idlelib/config_help.py --- a/Lib/idlelib/config_help.py Tue Jul 05 21:51:56 2016 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -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('', 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 -r adbeef96ae95 Lib/idlelib/configdialog.py --- a/Lib/idlelib/configdialog.py Tue Jul 05 21:51:56 2016 -0400 +++ b/Lib/idlelib/configdialog.py Wed Jul 06 23:42:44 2016 -0400 @@ -18,8 +18,7 @@ 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,7 @@ 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,9 +949,9 @@ 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 + newHelpSource = HelpSource( + self, 'Edit Help Source', menuitem=helpSource[0], + filepath=helpSource[1]).result if (not newHelpSource) or (newHelpSource == helpSource): return #no changes self.userHelpList[itemIndex] = newHelpSource diff -r adbeef96ae95 Lib/idlelib/idle_test/htest.py --- a/Lib/idlelib/idle_test/htest.py Tue Jul 05 21:51:56 2016 -0400 +++ b/Lib/idlelib/idle_test/htest.py Wed Jul 06 23:42:44 2016 -0400 @@ -137,18 +137,6 @@ "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 " - " and more than 30 chars are invalid menu item names.\n" - ", 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,20 @@ "should open that file \nin a new EditorWindow." } +HelpSource_spec = { + 'file': 'query', + 'kwds': {'title': 'Help name and source', + 'menuitem': 'test', + 'filepath': __file__, + '_htest': True}, + 'msg': "Enter menu item name and help file path\n " + " and more than 30 chars are invalid menu item names.\n" + ", file does not exist are invalid path items.\n" + "Test Browse with and without path, as cannot unittest.\n" + "A valid entry will be printed to shell with [0k].\n" + "[Cancel] will print None to shell" + } + _io_binding_spec = { 'file': 'iomenu', 'kwds': {}, @@ -241,7 +243,7 @@ '_htest': True}, 'msg': "Enter with 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 -r adbeef96ae95 Lib/idlelib/idle_test/test_config_help.py --- a/Lib/idlelib/idle_test/test_config_help.py Tue Jul 05 21:51:56 2016 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -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 -r adbeef96ae95 Lib/idlelib/idle_test/test_query.py --- a/Lib/idlelib/idle_test/test_query.py Tue Jul 05 21:51:56 2016 -0400 +++ b/Lib/idlelib/idle_test/test_query.py Wed Jul 06 23:42:44 2016 -0400 @@ -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 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 +# Mock entry.showerror messagebox so don't need click to continue +# when entry_ok and path_ok methods call it to display errors. -# entry_ok calls modal messagebox.showerror if entry is not ok. -# Mock showerrer so don't need to click to continue. orig_showerror = query.showerror showerror = Mbox_func() # Instance has __call__ method. @@ -34,7 +32,23 @@ 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 @@ 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 @@ 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 @@ 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 @@ 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 SectionNameTest(unittest.TestCase): + class Dummy_SectionName: + entry_ok = query.SectionName.entry_ok # Test override. + used_names = ['used'] + entry = Var() + dialog = Dummy_SectionName() def setUp(self): @@ -116,12 +142,15 @@ 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 ModuleNameTest(unittest.TestCase): + class Dummy_ModuleName: + entry_ok = query.ModuleName.entry_ok # Test override + text0 = '' + entry = Var() + + print(dir()) dialog = Dummy_ModuleName() def setUp(self): @@ -159,13 +188,120 @@ Equal(showerror.title, None) +# 3 HelpSource test classes + +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() + path = 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 ( + ('', lambda a,b,c:'', ''), + ('', lambda a,b,c: __file__, __file__), + ('htest', lambda a,b,c:'', 'htest'), + ('htest', lambda a,b,c: __file__, __file__)): + # We need all combination to test all (most) code paths. + with self.subTest(): + dialog.pathvar.set(path) + dialog.path.set(path) # Needed because not linked Entry. + 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 section_entry_ok(self): + return self.name + def path_ok(self): + return self.path + + dialog = Dummy_HelpSource() + + def test_entry_ok_helpsource(self): + for name, path, result in ((None, None, None), + (None, 'doc.txt', None), + ('doc', None, None), + ('doc', 'doc.txt', ('doc', 'doc.txt'))): + with self.subTest(): + self.dialog.name, self.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 +374,25 @@ 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 -r adbeef96ae95 Lib/idlelib/query.py --- a/Lib/idlelib/query.py Tue Jul 05 21:51:56 2016 -0400 +++ b/Lib/idlelib/query.py Wed Jul 06 23:42:44 2016 -0400 @@ -14,9 +14,16 @@ # Query and Section name result from splitting GetCfgSectionNameDialog # of configSectionNameDialog.py (temporarily config_sec.py) into # generic and specific parts. +# 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 +import sys +from sys import platform # so can set for test from tkinter import Toplevel, StringVar +from tkinter import filedialog from tkinter.messagebox import showerror from tkinter.ttk import Frame, Button, Entry, Label @@ -39,6 +46,7 @@ _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) @@ -52,7 +60,6 @@ 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( "+%d+%d" % ( parent.winfo_rootx() + @@ -67,9 +74,9 @@ 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) + 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 @@ 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) @@ -122,8 +129,8 @@ *, _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, + _htest=_htest, _utest=_utest) def entry_ok(self): "Return sensible ConfigParser section name or None." @@ -148,12 +155,12 @@ "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." @@ -187,9 +194,100 @@ return file_path +class HelpSource(SectionName): + "Get menu name and help source for Help menu." + + def __init__(self, parent, title, menuitem='', filepath='', + *, _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. + """ + # TODO Caller should pass names already used + #self.used_names = used_names # Then add this. + self.menuitem = menuitem + self.filepath = filepath + message = 'Name for item on Help menu:' + super().__init__(parent, title, message, {}, + _htest=_htest, _utest=_utest) + + def create_widgets(self): + super().create_widgets() + self.entry.insert(0, self.menuitem) + 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.path = Entry(frame, textvariable=self.pathvar, width=40) + self.path.insert(0, self.filepath) + + 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.path.get() + if path: + dir, base = os.path.split(path) + else: + base = None + if 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() + file = self.askfilename(filetypes, dir, base) + if file: + self.pathvar.set(file) + + section_entry_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 + 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 + if platform == 'darwin': # for Mac Safari + path = "file://" + path + return path + + def entry_ok(self): + "Return apparently valid (name, path) or None" + name = self.section_entry_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)