diff --git a/Lib/idlelib/idle_test/test_editor.py b/Lib/idlelib/idle_test/test_editor.py index fdb47abf43fb77..912de3391b7d06 100644 --- a/Lib/idlelib/idle_test/test_editor.py +++ b/Lib/idlelib/idle_test/test_editor.py @@ -1,12 +1,37 @@ "Test editor, coverage 35%." from idlelib import editor -import unittest from collections import namedtuple +from functools import partial +import sys from test.support import requires -from tkinter import Tk + +import unittest +from unittest import mock +import tkinter as tk +from idlelib.multicall import MultiCallCreator +from idlelib.idle_test.mock_idle import Func Editor = editor.EditorWindow +root = None +editwin = None + + +def setUpModule(): + global root, editwin + requires('gui') + root = tk.Tk() + root.withdraw() + editwin = editor.EditorWindow(root=root) + + +def tearDownModule(): + global root, editwin + editwin.close() + del editwin + root.update_idletasks() + root.destroy() + del root class EditorWindowTest(unittest.TestCase): @@ -14,7 +39,7 @@ class EditorWindowTest(unittest.TestCase): @classmethod def setUpClass(cls): requires('gui') - cls.root = Tk() + cls.root = tk.Tk() cls.root.withdraw() @classmethod @@ -103,7 +128,7 @@ class IndentAndNewlineTest(unittest.TestCase): @classmethod def setUpClass(cls): requires('gui') - cls.root = Tk() + cls.root = tk.Tk() cls.root.withdraw() cls.window = Editor(root=cls.root) cls.window.indentwidth = 2 @@ -178,7 +203,8 @@ def test_indent_and_newline_event(self): text.tag_add('sel', '1.17', '1.end') nl(None) # Deletes selected text before adding new line. - eq(get('1.0', 'end'), ' def f1(self, a,\n \n return a + b\n') + eq(get('1.0', 'end'), + ' def f1(self, a,\n \n return a + b\n') class RMenuTest(unittest.TestCase): @@ -186,7 +212,7 @@ class RMenuTest(unittest.TestCase): @classmethod def setUpClass(cls): requires('gui') - cls.root = Tk() + cls.root = tk.Tk() cls.root.withdraw() cls.window = Editor(root=cls.root) @@ -207,5 +233,389 @@ def test_rclick(self): pass +class ModuleHelpersTest(unittest.TestCase): + """Test functions defined at the module level.""" + + def test_prepstr(self): + ps = editor.prepstr + eq = self.assertEqual + eq(ps('_spam'), (0, 'spam')) + eq(ps('spam'), (-1, 'spam')) + eq(ps('spam_'), (4, 'spam')) + + @mock.patch.object(editor.macosx, 'isCocoaTk') + def test_get_accelerator(self, mock_cocoa): + ga = editor.get_accelerator + eq = self.assertEqual + keydefs = {'<>': [''], + '<>': [''], + '<>': [''], + '<>': [''], + '<>': ['', '']} + + mock_cocoa.return_value = False + eq(ga(keydefs, '<>'), '') # Not in keydefs. + eq(ga(keydefs, '<>'), 'Ctrl+J') # Control to Ctrl and first only. + eq(ga(keydefs, '<>'), 'Alt+9') # Remove Key-. + eq(ga(keydefs, '<>'), 'Alt+[') # bracketleft to [. + eq(ga(keydefs, '<>'), 'Ctrl+Break') # Cancel to Ctrl-Break. + eq(ga(keydefs, '<>'), 'Ctrl+Shift+O') # Shift doesn't change. + + # Cocoa test: it skips open-module shortcut. + mock_cocoa.return_value = True + eq(ga(keydefs, '<>'), '') + + +class MenubarTest(unittest.TestCase): + """Test functions involved with creating the menubar.""" + + @classmethod + def setUpClass(cls): + # Test the functions called during the __init__ for + # EditorWindow that create the menubar and submenus. + # The class is mocked in order to prevent the functions + # from being called automatically. + w = cls.mock_editwin = mock.Mock(editor.EditorWindow) + w.menubar = tk.Menu(root, tearoff=False) + w.text = tk.Text(root) + w.tkinter_vars = {} + + @classmethod + def tearDownClass(cls): + w = cls.mock_editwin + w.text.destroy() + w.menubar.destroy() + del w.menubar, w.text, w + + @mock.patch.object(editor.macosx, 'isCarbonTk') + def test_createmenubar(self, mock_mac): + eq = self.assertEqual + ed = editor.EditorWindow + w = self.mock_editwin + # Call real function instead of mock. + cmb = partial(editor.EditorWindow.createmenubar, w) + + # Load real editor menus. + w.menu_specs = ed.menu_specs + + mock_mac.return_value = False + cmb() + eq(list(w.menudict.keys()), + [name[0] for name in w.menu_specs]) + for index in range(w.menubar.index('end') + 1): + eq(w.menubar.type(index), tk.CASCADE) + eq(w.menubar.entrycget(index, 'label'), + editor.prepstr(w.menu_specs[index][1])[1]) + # Recent Files added here and not fill_menus. + eq(w.menudict['file'].entrycget(3, 'label'), 'Recent Files') + # No items added to helpmenu, so the length has no value. + eq(w.base_helpmenu_length, None) + w.fill_menus.assert_called_with() + w.reset_help_menu_entries.assert_called_with() + + # Carbon includes an application menu. + mock_mac.return_value = True + cmb() + eq(list(w.menudict.keys()), + [name[0] for name in w.menu_specs] + ['application']) + + def test_fill_menus(self): + eq = self.assertEqual + ed = editor.EditorWindow + w = self.mock_editwin + # Call real functions instead of mock. + fm = partial(editor.EditorWindow.fill_menus, w) + w.get_var_obj = ed.get_var_obj.__get__(w) + + # Initialize top level menubar. + w.menudict = {} + edit = w.menudict['edit'] = tk.Menu(w.menubar, name='edit', tearoff=False) + win = w.menudict['windows'] = tk.Menu(w.menubar, name='windows', tearoff=False) + form = w.menudict['format'] = tk.Menu(w.menubar, name='format', tearoff=False) + + # Submenus. + menudefs = [('edit', [('_New', '<>'), + None, + ('!Deb_ug', '<>')]), + ('shell', [('_View', '<>'), ]), + ('windows', [('Zoom Height', '<>')]), ] + keydefs = {'<>': ['']} + + fm(menudefs, keydefs) + eq(edit.type(0), tk.COMMAND) + eq(edit.entrycget(0, 'label'), 'New') + eq(edit.entrycget(0, 'underline'), 0) + self.assertIsNotNone(edit.entrycget(0, 'command')) + with self.assertRaises(tk.TclError): + self.assertIsNone(edit.entrycget(0, 'var')) + + eq(edit.type(1), tk.SEPARATOR) + with self.assertRaises(tk.TclError): + self.assertIsNone(edit.entrycget(1, 'label')) + + eq(edit.type(2), tk.CHECKBUTTON) + eq(edit.entrycget(2, 'label'), 'Debug') # Strip !. + eq(edit.entrycget(2, 'underline'), 3) # Check that underline ignores !. + self.assertIsNotNone(edit.entrycget(2, 'var')) + self.assertIn('<>', w.tkinter_vars) + + eq(win.entrycget(0, 'underline'), -1) + eq(win.entrycget(0, 'accelerator'), 'Alt+9') + + self.assertNotIn('shell', w.menudict) + + # Test defaults. + w.mainmenu.menudefs = ed.mainmenu.menudefs + w.mainmenu.default_keydefs = ed.mainmenu.default_keydefs + fm() + eq(form.index('end'), 9) # Default Format menu has 10 items. + self.assertNotIn('run', w.menudict) + + @mock.patch.object(editor.idleConf, 'GetAllExtraHelpSourcesList') + def test_reset_help_menu_entries(self, mock_extrahelp): + w = self.mock_editwin + mock_extrahelp.return_value = [('Python', 'https://python.org', '1')] + mock_callback = w._extra_help_callback + + # Create help menu. + help = w.menudict['help'] = tk.Menu(w.menubar, name='help', + tearoff=False) + cmd = mock_callback.return_value = lambda e: 'break' + help.add_command(label='help1', command=cmd) + w.base_helpmenu_length = help.index('end') + + # Add extra menu items that will be removed. + help.add_command(label='extra1', command=cmd) + help.add_command(label='extra2', command=cmd) + help.add_command(label='extra3', command=cmd) + help.add_command(label='extra4', command=cmd) + + # Assert that there are extra help items. + self.assertTrue(help.index('end') - w.base_helpmenu_length >= 4) + self.assertNotEqual(help.index('end'), w.base_helpmenu_length) + editor.EditorWindow.reset_help_menu_entries(w) + # Count is 2 because of separator. + self.assertEqual(help.index('end') - w.base_helpmenu_length, 2) + mock_callback.assert_called_with('https://python.org') + + def test_get_var_obj(self): + w = self.mock_editwin + gvo = partial(editor.EditorWindow.get_var_obj, w) + w.tkinter_vars = {} + + # No vartype. + self.assertIsNone(gvo('<>')) + self.assertNotIn('<>', w.tkinter_vars) + + # Create BooleanVar. + self.assertIsInstance(gvo('<>', tk.BooleanVar), + tk.BooleanVar) + self.assertIn('<>', w.tkinter_vars) + + # No vartype - check cache. + self.assertIsInstance(gvo('<>'), tk.BooleanVar) + + @mock.patch.object(editor.webbrowser, 'open') + @unittest.skipIf(sys.platform.startswith('win'), 'Unix only') + def test__extra_help_callback_not_windows(self, mock_openfile): + w = self.mock_editwin + ehc = partial(w._EditorWindow__extra_help_callback, w) + + ehc('http://python.org') + mock_openfile.called_with('http://python.org') + ehc('www.python.org') + mock_openfile.called_with('www.python.org') + ehc('/foo/bar/baz/') + mock_openfile.called_with('/foo/bar/baz') + + @mock.patch.object(editor.os, 'startfile') + @unittest.skipIf(not sys.platform.startswith('win'), 'Windows only') + def test_extra_help_callback_windows(self, mock_start): + # os.startfile doesn't exist on other platforms. + w = self.mock_editwin + w.showerror = mock.Mock() + def ehc(source): + return Editor._extra_help_callback(w, source) + ehc('http://python.org') + # 'called_with' requires spec tjr + #mock_start.called_with('http://python.org') + # Filename that doesn't open. + # But get '(X)' tk box 'Document Start Fa...' that needs OK click. + #mock_start.side_effect = OSError('boom') + ehc('/foo/bar/baz/')() + self.assertTrue(w.showerror.callargs.kwargs) + + +class BindingsTest(unittest.TestCase): + + def test_apply_bindings(self): + eq = self.assertEqual + w = editwin + # Save original text and recreate an empty version. It is not + # actually empty because Text widgets are created with default + # events. + orig_text = w.text + # Multicall has its own versions of the event_* methods. + text = w.text = MultiCallCreator(tk.Text)(root) + + keydefs = {'<>': [''], + '<>': [''], + '<>': [''], + '<>': [], + '<>': [''], + '<>': ['', '']} + + w.apply_bindings(keydefs) + eq(text.keydefs, keydefs) + # Multicall event_add() formats the key sequences. + eq(text.event_info('<>'), ('',)) + eq(text.event_info('<>'), ('', '')) + eq(text.event_info('<>'), ('',)) + # Although apply_bindings() skips events with no keys, Multicall + # event_info() just returns an empty tuple for undefined events. + eq(text.event_info('<>'), ()) + # Not in keydefs. + eq(text.event_info('<>'), ()) + + # Cleanup. + for event, keylist in keydefs.items(): + text.event_delete(event, *keylist) + + # Use default. + w.apply_bindings() + eq(text.event_info('<>'), ('',)) + + del w.text + w.text = orig_text + + +class ReloadTests(unittest.TestCase): + """Test functions called from configdialog for reloading attributes.""" + + @classmethod + def setUpClass(cls): + cls.keydefs = {'<>': ['', ''], + '<>': ['', ''], + '<>': [''], + '<>': [''], + '<>': [''], } + cls.extensions = {'<>'} + cls.ext_keydefs = {'<>': ['']} + + @classmethod + def tearDownClass(cls): + del cls.keydefs, cls.extensions, cls.ext_keydefs + + def setUp(self): + self.save_text = editwin.text + editwin.text = MultiCallCreator(tk.Text)(root) + + def tearDown(self): + del editwin.text + editwin.text = self.save_text + + @mock.patch.object(editor.idleConf, 'GetExtensionBindings') + @mock.patch.object(editor.idleConf, 'GetExtensions') + @mock.patch.object(editor.idleConf, 'GetCurrentKeySet') + def test_RemoveKeyBindings(self, mock_keyset, mock_ext, mock_ext_bindings): + eq = self.assertEqual + w = editwin + tei = w.text.event_info + keys = self.keydefs + extkeys = self.ext_keydefs + + mock_keyset.return_value = keys + mock_ext.return_value = self.extensions + mock_ext_bindings.return_value = extkeys + + w.apply_bindings(keys) + w.apply_bindings({'<>': ['']}) + w.apply_bindings(extkeys) + + # Bindings exist. + for event in keys: + self.assertNotEqual(tei(event), ()) + self.assertNotEqual(tei('<>'), ()) + # Extention bindings exist. + for event in extkeys: + self.assertNotEqual(tei(event), ()) + + w.RemoveKeybindings() + # Binding events have been deleted. + for event in keys: + eq(tei(event), ()) + # Extention bindings have been removed. + for event in extkeys: + eq(tei(event), ()) + # Extra keybindings are not removed - only removes those in idleConf. + self.assertNotEqual(tei('<>'), ()) + # Remove it. + w.text.event_delete('<>', ['']) + + @mock.patch.object(editor.idleConf, 'GetExtensionBindings') + @mock.patch.object(editor.idleConf, 'GetExtensions') + @mock.patch.object(editor.idleConf, 'GetCurrentKeySet') + def test_ApplyKeyBindings(self, mock_keyset, mock_ext, mock_ext_bindings): + eq = self.assertEqual + w = editwin + tei = w.text.event_info + keys = self.keydefs + extkeys = self.ext_keydefs + + mock_keyset.return_value = keys + mock_ext.return_value = self.extensions + mock_ext_bindings.return_value = extkeys + + # Bindings don't exist. + for event in keys: + eq(tei(event), ()) + # Extention bindings don't exist. + for event in extkeys: + eq(tei(event), ()) + + w.ApplyKeybindings() + eq(tei('<>'), ('',)) + eq(tei('<>'), ('', '')) + eq(tei('<>'), ('',)) + # Check menu accelerator update. + eq(w.menudict['help'].entrycget(3, 'accelerator'), 'F15') + + # Calling ApplyBindings is additive. + mock_keyset.return_value = {'<>': ['']} + w.ApplyKeybindings() + eq(tei('<>'), ('', '')) + w.text.event_delete('<>', ['']) + + mock_keyset.return_value = keys + w.RemoveKeybindings() + + def test_ResetColorizer(self): + pass + + @mock.patch.object(editor.idleConf, 'GetFont') + def test_ResetFont(self, mock_getfont): + mock_getfont.return_value = ('spam', 16, 'bold') + self.assertNotEqual(editwin.text['font'], 'spam 16 bold') + editwin.ResetFont() + self.assertEqual(editwin.text['font'], 'spam 16 bold') + + @mock.patch.object(editor.idleConf, 'GetOption') + def test_set_notabs_indentwidth(self, mock_get_option): + save_usetabs = editwin.usetabs + save_indentwidth = editwin.indentwidth + mock_get_option.return_value = 11 + + editwin.usetabs = True + editwin.set_notabs_indentwidth() + self.assertNotEqual(editwin.indentwidth, 11) + + editwin.usetabs = False + editwin.set_notabs_indentwidth() + self.assertEqual(editwin.indentwidth, 11) + + editwin.usetabs = save_usetabs + editwin.indentwidth = save_indentwidth + + if __name__ == '__main__': unittest.main(verbosity=2) diff --git a/Misc/NEWS.d/next/IDLE/2017-09-20-09-07-09.bpo-31529.w8ioyr.rst b/Misc/NEWS.d/next/IDLE/2017-09-20-09-07-09.bpo-31529.w8ioyr.rst new file mode 100644 index 00000000000000..3fc798866fd2ec --- /dev/null +++ b/Misc/NEWS.d/next/IDLE/2017-09-20-09-07-09.bpo-31529.w8ioyr.rst @@ -0,0 +1 @@ +IDLE: Add docstrings and unittests for some functions in editor.py. pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy