mirror of
				https://github.com/python/cpython.git
				synced 2025-10-31 21:51:50 +00:00 
			
		
		
		
	bpo-1612262: IDLE: Class Browser shows nested functions, classes (#2573)
Original patches for code and tests by Guilherme Polo and Cheryl Sabella, respectively.
This commit is contained in:
		
							parent
							
								
									0a1ff24acf
								
							
						
					
					
						commit
						058de11360
					
				
					 4 changed files with 328 additions and 156 deletions
				
			
		|  | @ -19,14 +19,49 @@ | ||||||
| from idlelib.tree import TreeNode, TreeItem, ScrolledCanvas | from idlelib.tree import TreeNode, TreeItem, ScrolledCanvas | ||||||
| from idlelib.windows import ListedToplevel | from idlelib.windows import ListedToplevel | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
| file_open = None  # Method...Item and Class...Item use this. | file_open = None  # Method...Item and Class...Item use this. | ||||||
| # Normally pyshell.flist.open, but there is no pyshell.flist for htest. | # Normally pyshell.flist.open, but there is no pyshell.flist for htest. | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
|  | def transform_children(child_dict, modname=None): | ||||||
|  |     """Transform a child dictionary to an ordered sequence of objects. | ||||||
|  | 
 | ||||||
|  |     The dictionary maps names to pyclbr information objects. | ||||||
|  |     Filter out imported objects. | ||||||
|  |     Augment class names with bases. | ||||||
|  |     Sort objects by line number. | ||||||
|  | 
 | ||||||
|  |     The current tree only calls this once per child_dic as it saves | ||||||
|  |     TreeItems once created.  A future tree and tests might violate this, | ||||||
|  |     so a check prevents multiple in-place augmentations. | ||||||
|  |     """ | ||||||
|  |     obs = []  # Use list since values should already be sorted. | ||||||
|  |     for key, obj in child_dict.items(): | ||||||
|  |         if modname is None or obj.module == modname: | ||||||
|  |             if hasattr(obj, 'super') and obj.super and obj.name == key: | ||||||
|  |                 # If obj.name != key, it has already been suffixed. | ||||||
|  |                 supers = [] | ||||||
|  |                 for sup in obj.super: | ||||||
|  |                     if type(sup) is type(''): | ||||||
|  |                         sname = sup | ||||||
|  |                     else: | ||||||
|  |                         sname = sup.name | ||||||
|  |                         if sup.module != obj.module: | ||||||
|  |                             sname = f'{sup.module}.{sname}' | ||||||
|  |                     supers.append(sname) | ||||||
|  |                 obj.name += '({})'.format(', '.join(supers)) | ||||||
|  |             obs.append(obj) | ||||||
|  |     return sorted(obs, key=lambda o: o.lineno) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
| class ClassBrowser: | class ClassBrowser: | ||||||
|     """Browse module classes and functions in IDLE. |     """Browse module classes and functions in IDLE. | ||||||
|     """ |     """ | ||||||
|  |     # This class is the base class for pathbrowser.PathBrowser. | ||||||
|  |     # Init and close are inherited, other methods are overriden. | ||||||
| 
 | 
 | ||||||
|     def __init__(self, flist, name, path, _htest=False): |     def __init__(self, flist, name, path, _htest=False, _utest=False): | ||||||
|         # XXX This API should change, if the file doesn't end in ".py" |         # XXX This API should change, if the file doesn't end in ".py" | ||||||
|         # XXX the code here is bogus! |         # XXX the code here is bogus! | ||||||
|         """Create a window for browsing a module's structure. |         """Create a window for browsing a module's structure. | ||||||
|  | @ -47,11 +82,12 @@ def __init__(self, flist, name, path, _htest=False): | ||||||
|                 the tree and subsequently in the children. |                 the tree and subsequently in the children. | ||||||
|         """ |         """ | ||||||
|         global file_open |         global file_open | ||||||
|         if not _htest: |         if not (_htest or _utest): | ||||||
|             file_open = pyshell.flist.open |             file_open = pyshell.flist.open | ||||||
|         self.name = name |         self.name = name | ||||||
|         self.file = os.path.join(path[0], self.name + ".py") |         self.file = os.path.join(path[0], self.name + ".py") | ||||||
|         self._htest = _htest |         self._htest = _htest | ||||||
|  |         self._utest = _utest | ||||||
|         self.init(flist) |         self.init(flist) | ||||||
| 
 | 
 | ||||||
|     def close(self, event=None): |     def close(self, event=None): | ||||||
|  | @ -80,6 +116,7 @@ def init(self, flist): | ||||||
|         sc.frame.pack(expand=1, fill="both") |         sc.frame.pack(expand=1, fill="both") | ||||||
|         item = self.rootnode() |         item = self.rootnode() | ||||||
|         self.node = node = TreeNode(sc.canvas, None, item) |         self.node = node = TreeNode(sc.canvas, None, item) | ||||||
|  |         if not self._utest: | ||||||
|             node.update() |             node.update() | ||||||
|             node.expand() |             node.expand() | ||||||
| 
 | 
 | ||||||
|  | @ -92,6 +129,7 @@ def rootnode(self): | ||||||
|         "Return a ModuleBrowserTreeItem as the root of the tree." |         "Return a ModuleBrowserTreeItem as the root of the tree." | ||||||
|         return ModuleBrowserTreeItem(self.file) |         return ModuleBrowserTreeItem(self.file) | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
| class ModuleBrowserTreeItem(TreeItem): | class ModuleBrowserTreeItem(TreeItem): | ||||||
|     """Browser tree for Python module. |     """Browser tree for Python module. | ||||||
| 
 | 
 | ||||||
|  | @ -115,16 +153,8 @@ def GetIconName(self): | ||||||
|         return "python" |         return "python" | ||||||
| 
 | 
 | ||||||
|     def GetSubList(self): |     def GetSubList(self): | ||||||
|         """Return the list of ClassBrowserTreeItem items. |         "Return ChildBrowserTreeItems for children." | ||||||
| 
 |         return [ChildBrowserTreeItem(obj) for obj in self.listchildren()] | ||||||
|         Each item returned from listclasses is the first level of |  | ||||||
|         classes/functions within the module. |  | ||||||
|         """ |  | ||||||
|         sublist = [] |  | ||||||
|         for name in self.listclasses(): |  | ||||||
|             item = ClassBrowserTreeItem(name, self.classes, self.file) |  | ||||||
|             sublist.append(item) |  | ||||||
|         return sublist |  | ||||||
| 
 | 
 | ||||||
|     def OnDoubleClick(self): |     def OnDoubleClick(self): | ||||||
|         "Open a module in an editor window when double clicked." |         "Open a module in an editor window when double clicked." | ||||||
|  | @ -132,89 +162,44 @@ def OnDoubleClick(self): | ||||||
|             return |             return | ||||||
|         if not os.path.exists(self.file): |         if not os.path.exists(self.file): | ||||||
|             return |             return | ||||||
|         pyshell.flist.open(self.file) |         file_open(self.file) | ||||||
| 
 | 
 | ||||||
|     def IsExpandable(self): |     def IsExpandable(self): | ||||||
|         "Return True if Python (.py) file." |         "Return True if Python (.py) file." | ||||||
|         return os.path.normcase(self.file[-3:]) == ".py" |         return os.path.normcase(self.file[-3:]) == ".py" | ||||||
| 
 | 
 | ||||||
|     def listclasses(self): |     def listchildren(self): | ||||||
|         """Return list of classes and functions in the module. |         "Return sequenced classes and functions in the module." | ||||||
| 
 |  | ||||||
|         The dictionary output from pyclbr is re-written as a |  | ||||||
|         list of tuples in the form (lineno, name) and |  | ||||||
|         then sorted so that the classes and functions are |  | ||||||
|         processed in line number order.  The returned list only |  | ||||||
|         contains the name and not the line number.  An instance |  | ||||||
|         variable self.classes contains the pyclbr dictionary values, |  | ||||||
|         which are instances of Class and Function. |  | ||||||
|         """ |  | ||||||
|         dir, file = os.path.split(self.file) |         dir, file = os.path.split(self.file) | ||||||
|         name, ext = os.path.splitext(file) |         name, ext = os.path.splitext(file) | ||||||
|         if os.path.normcase(ext) != ".py": |         if os.path.normcase(ext) != ".py": | ||||||
|             return [] |             return [] | ||||||
|         try: |         try: | ||||||
|             dict = pyclbr.readmodule_ex(name, [dir] + sys.path) |             tree = pyclbr.readmodule_ex(name, [dir] + sys.path) | ||||||
|         except ImportError: |         except ImportError: | ||||||
|             return [] |             return [] | ||||||
|         items = [] |         return transform_children(tree, name) | ||||||
|         self.classes = {} |  | ||||||
|         for key, cl in dict.items(): |  | ||||||
|             if cl.module == name: |  | ||||||
|                 s = key |  | ||||||
|                 if hasattr(cl, 'super') and cl.super: |  | ||||||
|                     supers = [] |  | ||||||
|                     for sup in cl.super: |  | ||||||
|                         if type(sup) is type(''): |  | ||||||
|                             sname = sup |  | ||||||
|                         else: |  | ||||||
|                             sname = sup.name |  | ||||||
|                             if sup.module != cl.module: |  | ||||||
|                                 sname = "%s.%s" % (sup.module, sname) |  | ||||||
|                         supers.append(sname) |  | ||||||
|                     s = s + "(%s)" % ", ".join(supers) |  | ||||||
|                 items.append((cl.lineno, s)) |  | ||||||
|                 self.classes[s] = cl |  | ||||||
|         items.sort() |  | ||||||
|         list = [] |  | ||||||
|         for item, s in items: |  | ||||||
|             list.append(s) |  | ||||||
|         return list |  | ||||||
| 
 | 
 | ||||||
| class ClassBrowserTreeItem(TreeItem): | 
 | ||||||
|     """Browser tree for classes within a module. | class ChildBrowserTreeItem(TreeItem): | ||||||
|  |     """Browser tree for child nodes within the module. | ||||||
| 
 | 
 | ||||||
|     Uses TreeItem as the basis for the structure of the tree. |     Uses TreeItem as the basis for the structure of the tree. | ||||||
|     """ |     """ | ||||||
| 
 | 
 | ||||||
|     def __init__(self, name, classes, file): |     def __init__(self, obj): | ||||||
|         """Create a TreeItem for the class/function. |         "Create a TreeItem for a pyclbr class/function object." | ||||||
| 
 |         self.obj = obj | ||||||
|         Args: |         self.name = obj.name | ||||||
|             name: Name of the class/function. |         self.isfunction = isinstance(obj, pyclbr.Function) | ||||||
|             classes: Dictonary of Class/Function instances from pyclbr. |  | ||||||
|             file: Full path and module name. |  | ||||||
| 
 |  | ||||||
|         Instance variables: |  | ||||||
|             self.cl: Class/Function instance for the class/function name. |  | ||||||
|             self.isfunction: True if self.cl is a Function. |  | ||||||
|         """ |  | ||||||
|         self.name = name |  | ||||||
|         # XXX - Does classes need to be an instance variable? |  | ||||||
|         self.classes = classes |  | ||||||
|         self.file = file |  | ||||||
|         try: |  | ||||||
|             self.cl = self.classes[self.name] |  | ||||||
|         except (IndexError, KeyError): |  | ||||||
|             self.cl = None |  | ||||||
|         self.isfunction = isinstance(self.cl, pyclbr.Function) |  | ||||||
| 
 | 
 | ||||||
|     def GetText(self): |     def GetText(self): | ||||||
|         "Return the name of the function/class to display." |         "Return the name of the function/class to display." | ||||||
|  |         name = self.name | ||||||
|         if self.isfunction: |         if self.isfunction: | ||||||
|             return "def " + self.name + "(...)" |             return "def " + name + "(...)" | ||||||
|         else: |         else: | ||||||
|             return "class " + self.name |             return "class " + name | ||||||
| 
 | 
 | ||||||
|     def GetIconName(self): |     def GetIconName(self): | ||||||
|         "Return the name of the icon to display." |         "Return the name of the icon to display." | ||||||
|  | @ -224,95 +209,34 @@ def GetIconName(self): | ||||||
|             return "folder" |             return "folder" | ||||||
| 
 | 
 | ||||||
|     def IsExpandable(self): |     def IsExpandable(self): | ||||||
|         "Return True if this class has methods." |         "Return True if self.obj has nested objects." | ||||||
|         if self.cl: |         return self.obj.children != {} | ||||||
|             try: |  | ||||||
|                 return not not self.cl.methods |  | ||||||
|             except AttributeError: |  | ||||||
|                 return False |  | ||||||
|         return None |  | ||||||
| 
 | 
 | ||||||
|     def GetSubList(self): |     def GetSubList(self): | ||||||
|         """Return Class methods as a list of MethodBrowserTreeItem items. |         "Return ChildBrowserTreeItems for children." | ||||||
| 
 |         return [ChildBrowserTreeItem(obj) | ||||||
|         Each item is a method within the class. |                 for obj in transform_children(self.obj.children)] | ||||||
|         """ |  | ||||||
|         if not self.cl: |  | ||||||
|             return [] |  | ||||||
|         sublist = [] |  | ||||||
|         for name in self.listmethods(): |  | ||||||
|             item = MethodBrowserTreeItem(name, self.cl, self.file) |  | ||||||
|             sublist.append(item) |  | ||||||
|         return sublist |  | ||||||
| 
 | 
 | ||||||
|     def OnDoubleClick(self): |     def OnDoubleClick(self): | ||||||
|         "Open module with file_open and position to lineno, if it exists." |         "Open module with file_open and position to lineno." | ||||||
|         if not os.path.exists(self.file): |         try: | ||||||
|             return |             edit = file_open(self.obj.file) | ||||||
|         edit = file_open(self.file) |             edit.gotoline(self.obj.lineno) | ||||||
|         if hasattr(self.cl, 'lineno'): |         except (OSError, AttributeError): | ||||||
|             lineno = self.cl.lineno |             pass | ||||||
|             edit.gotoline(lineno) |  | ||||||
| 
 | 
 | ||||||
|     def listmethods(self): |  | ||||||
|         "Return list of methods within a class sorted by lineno." |  | ||||||
|         if not self.cl: |  | ||||||
|             return [] |  | ||||||
|         items = [] |  | ||||||
|         for name, lineno in self.cl.methods.items(): |  | ||||||
|             items.append((lineno, name)) |  | ||||||
|         items.sort() |  | ||||||
|         list = [] |  | ||||||
|         for item, name in items: |  | ||||||
|             list.append(name) |  | ||||||
|         return list |  | ||||||
| 
 |  | ||||||
| class MethodBrowserTreeItem(TreeItem): |  | ||||||
|     """Browser tree for methods within a class. |  | ||||||
| 
 |  | ||||||
|     Uses TreeItem as the basis for the structure of the tree. |  | ||||||
|     """ |  | ||||||
| 
 |  | ||||||
|     def __init__(self, name, cl, file): |  | ||||||
|         """Create a TreeItem for the methods. |  | ||||||
| 
 |  | ||||||
|         Args: |  | ||||||
|             name: Name of the class/function. |  | ||||||
|             cl: pyclbr.Class instance for name. |  | ||||||
|             file: Full path and module name. |  | ||||||
|         """ |  | ||||||
|         self.name = name |  | ||||||
|         self.cl = cl |  | ||||||
|         self.file = file |  | ||||||
| 
 |  | ||||||
|     def GetText(self): |  | ||||||
|         "Return the method name to display." |  | ||||||
|         return "def " + self.name + "(...)" |  | ||||||
| 
 |  | ||||||
|     def GetIconName(self): |  | ||||||
|         "Return the name of the icon to display." |  | ||||||
|         return "python" |  | ||||||
| 
 |  | ||||||
|     def IsExpandable(self): |  | ||||||
|         "Return False as there are no tree items after methods." |  | ||||||
|         return False |  | ||||||
| 
 |  | ||||||
|     def OnDoubleClick(self): |  | ||||||
|         "Open module with file_open and position at the method start." |  | ||||||
|         if not os.path.exists(self.file): |  | ||||||
|             return |  | ||||||
|         edit = file_open(self.file) |  | ||||||
|         edit.gotoline(self.cl.methods[self.name]) |  | ||||||
| 
 | 
 | ||||||
| def _class_browser(parent): # htest # | def _class_browser(parent): # htest # | ||||||
|     try: |     try: | ||||||
|  |         file = sys.argv[1]  # If pass file on command line | ||||||
|  |         # If this succeeds, unittest will fail. | ||||||
|  |     except IndexError: | ||||||
|         file = __file__ |         file = __file__ | ||||||
|     except NameError: |         # Add objects for htest | ||||||
|         file = sys.argv[0] |         class Nested_in_func(TreeNode): | ||||||
|         if sys.argv[1:]: |             def nested_in_class(): pass | ||||||
|             file = sys.argv[1] |         def closure(): | ||||||
|         else: |             class Nested_in_closure: pass | ||||||
|             file = sys.argv[0] |  | ||||||
|     dir, file = os.path.split(file) |     dir, file = os.path.split(file) | ||||||
|     name = os.path.splitext(file)[0] |     name = os.path.splitext(file)[0] | ||||||
|     flist = pyshell.PyShellFileList(parent) |     flist = pyshell.PyShellFileList(parent) | ||||||
|  | @ -321,5 +245,7 @@ def _class_browser(parent): # htest # | ||||||
|     ClassBrowser(flist, name, [dir], _htest=True) |     ClassBrowser(flist, name, [dir], _htest=True) | ||||||
| 
 | 
 | ||||||
| if __name__ == "__main__": | if __name__ == "__main__": | ||||||
|  |     from unittest import main | ||||||
|  |     main('idlelib.idle_test.test_browser', verbosity=2, exit=False) | ||||||
|     from idlelib.idle_test.htest import run |     from idlelib.idle_test.htest import run | ||||||
|     run(_class_browser) |     run(_class_browser) | ||||||
|  |  | ||||||
							
								
								
									
										242
									
								
								Lib/idlelib/idle_test/test_browser.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										242
									
								
								Lib/idlelib/idle_test/test_browser.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,242 @@ | ||||||
|  | """ Test idlelib.browser. | ||||||
|  | 
 | ||||||
|  | Coverage: 88% | ||||||
|  | (Higher, because should exclude 3 lines that .coveragerc won't exclude.) | ||||||
|  | """ | ||||||
|  | 
 | ||||||
|  | import os.path | ||||||
|  | import unittest | ||||||
|  | import pyclbr | ||||||
|  | 
 | ||||||
|  | from idlelib import browser, filelist | ||||||
|  | from idlelib.tree import TreeNode | ||||||
|  | from test.support import requires | ||||||
|  | from unittest import mock | ||||||
|  | from tkinter import Tk | ||||||
|  | from idlelib.idle_test.mock_idle import Func | ||||||
|  | from collections import deque | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class ClassBrowserTest(unittest.TestCase): | ||||||
|  | 
 | ||||||
|  |     @classmethod | ||||||
|  |     def setUpClass(cls): | ||||||
|  |         requires('gui') | ||||||
|  |         cls.root = Tk() | ||||||
|  |         cls.root.withdraw() | ||||||
|  |         cls.flist = filelist.FileList(cls.root) | ||||||
|  |         cls.file = __file__ | ||||||
|  |         cls.path = os.path.dirname(cls.file) | ||||||
|  |         cls.module = os.path.basename(cls.file).rstrip('.py') | ||||||
|  |         cls.cb = browser.ClassBrowser(cls.flist, cls.module, [cls.path], _utest=True) | ||||||
|  | 
 | ||||||
|  |     @classmethod | ||||||
|  |     def tearDownClass(cls): | ||||||
|  |         cls.cb.close() | ||||||
|  |         cls.root.destroy() | ||||||
|  |         del cls.root, cls.flist, cls.cb | ||||||
|  | 
 | ||||||
|  |     def test_init(self): | ||||||
|  |         cb = self.cb | ||||||
|  |         eq = self.assertEqual | ||||||
|  |         eq(cb.name, self.module) | ||||||
|  |         eq(cb.file, self.file) | ||||||
|  |         eq(cb.flist, self.flist) | ||||||
|  |         eq(pyclbr._modules, {}) | ||||||
|  |         self.assertIsInstance(cb.node, TreeNode) | ||||||
|  | 
 | ||||||
|  |     def test_settitle(self): | ||||||
|  |         cb = self.cb | ||||||
|  |         self.assertIn(self.module, cb.top.title()) | ||||||
|  |         self.assertEqual(cb.top.iconname(), 'Class Browser') | ||||||
|  | 
 | ||||||
|  |     def test_rootnode(self): | ||||||
|  |         cb = self.cb | ||||||
|  |         rn = cb.rootnode() | ||||||
|  |         self.assertIsInstance(rn, browser.ModuleBrowserTreeItem) | ||||||
|  | 
 | ||||||
|  |     def test_close(self): | ||||||
|  |         cb = self.cb | ||||||
|  |         cb.top.destroy = Func() | ||||||
|  |         cb.node.destroy = Func() | ||||||
|  |         cb.close() | ||||||
|  |         self.assertTrue(cb.top.destroy.called) | ||||||
|  |         self.assertTrue(cb.node.destroy.called) | ||||||
|  |         del cb.top.destroy, cb.node.destroy | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | # Same nested tree creation as in test_pyclbr.py except for super on C0. | ||||||
|  | mb = pyclbr | ||||||
|  | module, fname = 'test', 'test.py' | ||||||
|  | f0 = mb.Function(module, 'f0', fname, 1) | ||||||
|  | f1 = mb._nest_function(f0, 'f1', 2) | ||||||
|  | f2 = mb._nest_function(f1, 'f2', 3) | ||||||
|  | c1 = mb._nest_class(f0, 'c1', 5) | ||||||
|  | C0 = mb.Class(module, 'C0', ['base'], fname, 6) | ||||||
|  | F1 = mb._nest_function(C0, 'F1', 8) | ||||||
|  | C1 = mb._nest_class(C0, 'C1', 11, ['']) | ||||||
|  | C2 = mb._nest_class(C1, 'C2', 12) | ||||||
|  | F3 = mb._nest_function(C2, 'F3', 14) | ||||||
|  | mock_pyclbr_tree = {'f0': f0, 'C0': C0} | ||||||
|  | 
 | ||||||
|  | # transform_children(mock_pyclbr_tree, 'test') mutates C0.name. | ||||||
|  | 
 | ||||||
|  | class TransformChildrenTest(unittest.TestCase): | ||||||
|  | 
 | ||||||
|  |     def test_transform_children(self): | ||||||
|  |         eq = self.assertEqual | ||||||
|  |         # Parameter matches tree module. | ||||||
|  |         tcl = list(browser.transform_children(mock_pyclbr_tree, 'test')) | ||||||
|  |         eq(tcl[0], f0) | ||||||
|  |         eq(tcl[1], C0) | ||||||
|  |         eq(tcl[1].name, 'C0(base)') | ||||||
|  |         # Check that second call does not add second '(base)' suffix. | ||||||
|  |         tcl = list(browser.transform_children(mock_pyclbr_tree, 'test')) | ||||||
|  |         eq(tcl[1].name, 'C0(base)') | ||||||
|  |         # Nothing to traverse if parameter name isn't same as tree module. | ||||||
|  |         tn = browser.transform_children(mock_pyclbr_tree, 'different name') | ||||||
|  |         self.assertEqual(list(tn), []) | ||||||
|  |         # No name parameter. | ||||||
|  |         tn = browser.transform_children({'f1': f1, 'c1': c1}) | ||||||
|  |         self.assertEqual(list(tn), [f1, c1]) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class ModuleBrowserTreeItemTest(unittest.TestCase): | ||||||
|  | 
 | ||||||
|  |     @classmethod | ||||||
|  |     def setUpClass(cls): | ||||||
|  |         cls.mbt = browser.ModuleBrowserTreeItem(fname) | ||||||
|  | 
 | ||||||
|  |     def test_init(self): | ||||||
|  |         self.assertEqual(self.mbt.file, fname) | ||||||
|  | 
 | ||||||
|  |     def test_gettext(self): | ||||||
|  |         self.assertEqual(self.mbt.GetText(), fname) | ||||||
|  | 
 | ||||||
|  |     def test_geticonname(self): | ||||||
|  |         self.assertEqual(self.mbt.GetIconName(), 'python') | ||||||
|  | 
 | ||||||
|  |     def test_isexpandable(self): | ||||||
|  |         self.assertTrue(self.mbt.IsExpandable()) | ||||||
|  | 
 | ||||||
|  |     def test_listchildren(self): | ||||||
|  |         save_rex = browser.pyclbr.readmodule_ex | ||||||
|  |         save_tc = browser.transform_children | ||||||
|  |         browser.pyclbr.readmodule_ex = Func(result=mock_pyclbr_tree) | ||||||
|  |         browser.transform_children = Func(result=[f0, C0]) | ||||||
|  |         try: | ||||||
|  |             self.assertEqual(self.mbt.listchildren(), [f0, C0]) | ||||||
|  |         finally: | ||||||
|  |             browser.pyclbr.readmodule_ex = save_rex | ||||||
|  |             browser.transform_children = save_tc | ||||||
|  | 
 | ||||||
|  |     def test_getsublist(self): | ||||||
|  |         mbt = self.mbt | ||||||
|  |         mbt.listchildren = Func(result=[f0, C0]) | ||||||
|  |         sub0, sub1 = mbt.GetSubList() | ||||||
|  |         del mbt.listchildren | ||||||
|  |         self.assertIsInstance(sub0, browser.ChildBrowserTreeItem) | ||||||
|  |         self.assertIsInstance(sub1, browser.ChildBrowserTreeItem) | ||||||
|  |         self.assertEqual(sub0.name, 'f0') | ||||||
|  |         self.assertEqual(sub1.name, 'C0') | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |     def test_ondoubleclick(self): | ||||||
|  |         mbt = self.mbt | ||||||
|  |         fopen = browser.file_open = mock.Mock() | ||||||
|  | 
 | ||||||
|  |         with mock.patch('os.path.exists', return_value=False): | ||||||
|  |             mbt.OnDoubleClick() | ||||||
|  |             fopen.assert_not_called() | ||||||
|  | 
 | ||||||
|  |         with mock.patch('os.path.exists', return_value=True): | ||||||
|  |             mbt.OnDoubleClick() | ||||||
|  |             fopen.assert_called() | ||||||
|  |             fopen.called_with(fname) | ||||||
|  | 
 | ||||||
|  |         del browser.file_open | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class ChildBrowserTreeItemTest(unittest.TestCase): | ||||||
|  | 
 | ||||||
|  |     @classmethod | ||||||
|  |     def setUpClass(cls): | ||||||
|  |         CBT = browser.ChildBrowserTreeItem | ||||||
|  |         cls.cbt_f1 = CBT(f1) | ||||||
|  |         cls.cbt_C1 = CBT(C1) | ||||||
|  |         cls.cbt_F1 = CBT(F1) | ||||||
|  | 
 | ||||||
|  |     @classmethod | ||||||
|  |     def tearDownClass(cls): | ||||||
|  |         del cls.cbt_C1, cls.cbt_f1, cls.cbt_F1 | ||||||
|  | 
 | ||||||
|  |     def test_init(self): | ||||||
|  |         eq = self.assertEqual | ||||||
|  |         eq(self.cbt_C1.name, 'C1') | ||||||
|  |         self.assertFalse(self.cbt_C1.isfunction) | ||||||
|  |         eq(self.cbt_f1.name, 'f1') | ||||||
|  |         self.assertTrue(self.cbt_f1.isfunction) | ||||||
|  | 
 | ||||||
|  |     def test_gettext(self): | ||||||
|  |         self.assertEqual(self.cbt_C1.GetText(), 'class C1') | ||||||
|  |         self.assertEqual(self.cbt_f1.GetText(), 'def f1(...)') | ||||||
|  | 
 | ||||||
|  |     def test_geticonname(self): | ||||||
|  |         self.assertEqual(self.cbt_C1.GetIconName(), 'folder') | ||||||
|  |         self.assertEqual(self.cbt_f1.GetIconName(), 'python') | ||||||
|  | 
 | ||||||
|  |     def test_isexpandable(self): | ||||||
|  |         self.assertTrue(self.cbt_C1.IsExpandable()) | ||||||
|  |         self.assertTrue(self.cbt_f1.IsExpandable()) | ||||||
|  |         self.assertFalse(self.cbt_F1.IsExpandable()) | ||||||
|  | 
 | ||||||
|  |     def test_getsublist(self): | ||||||
|  |         eq = self.assertEqual | ||||||
|  |         CBT = browser.ChildBrowserTreeItem | ||||||
|  | 
 | ||||||
|  |         f1sublist = self.cbt_f1.GetSubList() | ||||||
|  |         self.assertIsInstance(f1sublist[0], CBT) | ||||||
|  |         eq(len(f1sublist), 1) | ||||||
|  |         eq(f1sublist[0].name, 'f2') | ||||||
|  | 
 | ||||||
|  |         eq(self.cbt_F1.GetSubList(), []) | ||||||
|  | 
 | ||||||
|  |     def test_ondoubleclick(self): | ||||||
|  |         fopen = browser.file_open = mock.Mock() | ||||||
|  |         goto = fopen.return_value.gotoline = mock.Mock() | ||||||
|  |         self.cbt_F1.OnDoubleClick() | ||||||
|  |         fopen.assert_called() | ||||||
|  |         goto.assert_called() | ||||||
|  |         goto.assert_called_with(self.cbt_F1.obj.lineno) | ||||||
|  |         del browser.file_open | ||||||
|  |         # Failure test would have to raise OSError or AttributeError. | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class NestedChildrenTest(unittest.TestCase): | ||||||
|  |     "Test that all the nodes in a nested tree are added to the BrowserTree." | ||||||
|  | 
 | ||||||
|  |     def test_nested(self): | ||||||
|  |         queue = deque() | ||||||
|  |         actual_names = [] | ||||||
|  |         # The tree items are processed in breadth first order. | ||||||
|  |         # Verify that processing each sublist hits every node and | ||||||
|  |         # in the right order. | ||||||
|  |         expected_names = ['f0', 'C0',  # This is run before transform test. | ||||||
|  |                           'f1', 'c1', 'F1', 'C1()', | ||||||
|  |                           'f2', 'C2', | ||||||
|  |                           'F3'] | ||||||
|  |         CBT = browser.ChildBrowserTreeItem | ||||||
|  |         queue.extend((CBT(f0), CBT(C0))) | ||||||
|  |         while queue: | ||||||
|  |             cb = queue.popleft() | ||||||
|  |             sublist = cb.GetSubList() | ||||||
|  |             queue.extend(sublist) | ||||||
|  |             self.assertIn(cb.name, cb.GetText()) | ||||||
|  |             self.assertIn(cb.GetIconName(), ('python', 'folder')) | ||||||
|  |             self.assertIs(cb.IsExpandable(), sublist != []) | ||||||
|  |             actual_names.append(cb.name) | ||||||
|  |         self.assertEqual(actual_names, expected_names) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | if __name__ == '__main__': | ||||||
|  |     unittest.main(verbosity=2) | ||||||
|  | @ -9,11 +9,12 @@ | ||||||
| 
 | 
 | ||||||
| class PathBrowser(ClassBrowser): | class PathBrowser(ClassBrowser): | ||||||
| 
 | 
 | ||||||
|     def __init__(self, flist, _htest=False): |     def __init__(self, flist, _htest=False, _utest=False): | ||||||
|         """ |         """ | ||||||
|         _htest - bool, change box location when running htest |         _htest - bool, change box location when running htest | ||||||
|         """ |         """ | ||||||
|         self._htest = _htest |         self._htest = _htest | ||||||
|  |         self._utest = _utest | ||||||
|         self.init(flist) |         self.init(flist) | ||||||
| 
 | 
 | ||||||
|     def settitle(self): |     def settitle(self): | ||||||
|  |  | ||||||
|  | @ -0,0 +1,3 @@ | ||||||
|  | IDLE module browser now shows nested classes and functions. | ||||||
|  | Original patches for code and tests by Guilherme Polo and | ||||||
|  | Cheryl Sabella, respectively. | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Cheryl Sabella
						Cheryl Sabella