1"Test browser, coverage 90%."
2
3from idlelib import browser
4from test.support import requires
5import unittest
6from unittest import mock
7from idlelib.idle_test.mock_idle import Func
8from idlelib.util import py_extensions
9
10from collections import deque
11import os.path
12import pyclbr
13from tkinter import Tk
14
15from idlelib.tree import TreeNode
16
17
18class ModuleBrowserTest(unittest.TestCase):
19
20    @classmethod
21    def setUpClass(cls):
22        requires('gui')
23        cls.root = Tk()
24        cls.root.withdraw()
25        cls.mb = browser.ModuleBrowser(cls.root, __file__, _utest=True)
26
27    @classmethod
28    def tearDownClass(cls):
29        cls.mb.close()
30        cls.root.update_idletasks()
31        cls.root.destroy()
32        del cls.root, cls.mb
33
34    def test_init(self):
35        mb = self.mb
36        eq = self.assertEqual
37        eq(mb.path, __file__)
38        eq(pyclbr._modules, {})
39        self.assertIsInstance(mb.node, TreeNode)
40        self.assertIsNotNone(browser.file_open)
41
42    def test_settitle(self):
43        mb = self.mb
44        self.assertIn(os.path.basename(__file__), mb.top.title())
45        self.assertEqual(mb.top.iconname(), 'Module Browser')
46
47    def test_rootnode(self):
48        mb = self.mb
49        rn = mb.rootnode()
50        self.assertIsInstance(rn, browser.ModuleBrowserTreeItem)
51
52    def test_close(self):
53        mb = self.mb
54        mb.top.destroy = Func()
55        mb.node.destroy = Func()
56        mb.close()
57        self.assertTrue(mb.top.destroy.called)
58        self.assertTrue(mb.node.destroy.called)
59        del mb.top.destroy, mb.node.destroy
60
61    def test_is_browseable_extension(self):
62        path = "/path/to/file"
63        for ext in py_extensions:
64            with self.subTest(ext=ext):
65                filename = f'{path}{ext}'
66                actual = browser.is_browseable_extension(filename)
67                expected = ext not in browser.browseable_extension_blocklist
68                self.assertEqual(actual, expected)
69
70
71# Nested tree same as in test_pyclbr.py except for supers on C0. C1.
72mb = pyclbr
73module, fname = 'test', 'test.py'
74C0 = mb.Class(module, 'C0', ['base'], fname, 1, end_lineno=9)
75F1 = mb._nest_function(C0, 'F1', 3, 5)
76C1 = mb._nest_class(C0, 'C1', 6, 9, [''])
77C2 = mb._nest_class(C1, 'C2', 7, 9)
78F3 = mb._nest_function(C2, 'F3', 9, 9)
79f0 = mb.Function(module, 'f0', fname, 11, end_lineno=15)
80f1 = mb._nest_function(f0, 'f1', 12, 14)
81f2 = mb._nest_function(f1, 'f2', 13, 13)
82c1 = mb._nest_class(f0, 'c1', 15, 15)
83mock_pyclbr_tree = {'C0': C0, 'f0': f0}
84
85# Adjust C0.name, C1.name so tests do not depend on order.
86browser.transform_children(mock_pyclbr_tree, 'test')  # C0(base)
87browser.transform_children(C0.children)  # C1()
88
89# The class below checks that the calls above are correct
90# and that duplicate calls have no effect.
91
92
93class TransformChildrenTest(unittest.TestCase):
94
95    def test_transform_module_children(self):
96        eq = self.assertEqual
97        transform = browser.transform_children
98        # Parameter matches tree module.
99        tcl = list(transform(mock_pyclbr_tree, 'test'))
100        eq(tcl, [C0, f0])
101        eq(tcl[0].name, 'C0(base)')
102        eq(tcl[1].name, 'f0')
103        # Check that second call does not change suffix.
104        tcl = list(transform(mock_pyclbr_tree, 'test'))
105        eq(tcl[0].name, 'C0(base)')
106        # Nothing to traverse if parameter name isn't same as tree module.
107        tcl = list(transform(mock_pyclbr_tree, 'different name'))
108        eq(tcl, [])
109
110    def test_transform_node_children(self):
111        eq = self.assertEqual
112        transform = browser.transform_children
113        # Class with two children, one name altered.
114        tcl = list(transform(C0.children))
115        eq(tcl, [F1, C1])
116        eq(tcl[0].name, 'F1')
117        eq(tcl[1].name, 'C1()')
118        tcl = list(transform(C0.children))
119        eq(tcl[1].name, 'C1()')
120        # Function with two children.
121        eq(list(transform(f0.children)), [f1, c1])
122
123
124class ModuleBrowserTreeItemTest(unittest.TestCase):
125
126    @classmethod
127    def setUpClass(cls):
128        cls.mbt = browser.ModuleBrowserTreeItem(fname)
129
130    def test_init(self):
131        self.assertEqual(self.mbt.file, fname)
132
133    def test_gettext(self):
134        self.assertEqual(self.mbt.GetText(), fname)
135
136    def test_geticonname(self):
137        self.assertEqual(self.mbt.GetIconName(), 'python')
138
139    def test_isexpandable(self):
140        self.assertTrue(self.mbt.IsExpandable())
141
142    def test_listchildren(self):
143        save_rex = browser.pyclbr.readmodule_ex
144        save_tc = browser.transform_children
145        browser.pyclbr.readmodule_ex = Func(result=mock_pyclbr_tree)
146        browser.transform_children = Func(result=[f0, C0])
147        try:
148            self.assertEqual(self.mbt.listchildren(), [f0, C0])
149        finally:
150            browser.pyclbr.readmodule_ex = save_rex
151            browser.transform_children = save_tc
152
153    def test_getsublist(self):
154        mbt = self.mbt
155        mbt.listchildren = Func(result=[f0, C0])
156        sub0, sub1 = mbt.GetSubList()
157        del mbt.listchildren
158        self.assertIsInstance(sub0, browser.ChildBrowserTreeItem)
159        self.assertIsInstance(sub1, browser.ChildBrowserTreeItem)
160        self.assertEqual(sub0.name, 'f0')
161        self.assertEqual(sub1.name, 'C0(base)')
162
163    @mock.patch('idlelib.browser.file_open')
164    def test_ondoubleclick(self, fopen):
165        mbt = self.mbt
166
167        with mock.patch('os.path.exists', return_value=False):
168            mbt.OnDoubleClick()
169            fopen.assert_not_called()
170
171        with mock.patch('os.path.exists', return_value=True):
172            mbt.OnDoubleClick()
173            fopen.assert_called_once_with(fname)
174
175
176class ChildBrowserTreeItemTest(unittest.TestCase):
177
178    @classmethod
179    def setUpClass(cls):
180        CBT = browser.ChildBrowserTreeItem
181        cls.cbt_f1 = CBT(f1)
182        cls.cbt_C1 = CBT(C1)
183        cls.cbt_F1 = CBT(F1)
184
185    @classmethod
186    def tearDownClass(cls):
187        del cls.cbt_C1, cls.cbt_f1, cls.cbt_F1
188
189    def test_init(self):
190        eq = self.assertEqual
191        eq(self.cbt_C1.name, 'C1()')
192        self.assertFalse(self.cbt_C1.isfunction)
193        eq(self.cbt_f1.name, 'f1')
194        self.assertTrue(self.cbt_f1.isfunction)
195
196    def test_gettext(self):
197        self.assertEqual(self.cbt_C1.GetText(), 'class C1()')
198        self.assertEqual(self.cbt_f1.GetText(), 'def f1(...)')
199
200    def test_geticonname(self):
201        self.assertEqual(self.cbt_C1.GetIconName(), 'folder')
202        self.assertEqual(self.cbt_f1.GetIconName(), 'python')
203
204    def test_isexpandable(self):
205        self.assertTrue(self.cbt_C1.IsExpandable())
206        self.assertTrue(self.cbt_f1.IsExpandable())
207        self.assertFalse(self.cbt_F1.IsExpandable())
208
209    def test_getsublist(self):
210        eq = self.assertEqual
211        CBT = browser.ChildBrowserTreeItem
212
213        f1sublist = self.cbt_f1.GetSubList()
214        self.assertIsInstance(f1sublist[0], CBT)
215        eq(len(f1sublist), 1)
216        eq(f1sublist[0].name, 'f2')
217
218        eq(self.cbt_F1.GetSubList(), [])
219
220    @mock.patch('idlelib.browser.file_open')
221    def test_ondoubleclick(self, fopen):
222        goto = fopen.return_value.gotoline = mock.Mock()
223        self.cbt_F1.OnDoubleClick()
224        fopen.assert_called()
225        goto.assert_called()
226        goto.assert_called_with(self.cbt_F1.obj.lineno)
227        # Failure test would have to raise OSError or AttributeError.
228
229
230class NestedChildrenTest(unittest.TestCase):
231    "Test that all the nodes in a nested tree are added to the BrowserTree."
232
233    def test_nested(self):
234        queue = deque()
235        actual_names = []
236        # The tree items are processed in breadth first order.
237        # Verify that processing each sublist hits every node and
238        # in the right order.
239        expected_names = ['f0', 'C0(base)',
240                          'f1', 'c1', 'F1', 'C1()',
241                          'f2', 'C2',
242                          'F3']
243        CBT = browser.ChildBrowserTreeItem
244        queue.extend((CBT(f0), CBT(C0)))
245        while queue:
246            cb = queue.popleft()
247            sublist = cb.GetSubList()
248            queue.extend(sublist)
249            self.assertIn(cb.name, cb.GetText())
250            self.assertIn(cb.GetIconName(), ('python', 'folder'))
251            self.assertIs(cb.IsExpandable(), sublist != [])
252            actual_names.append(cb.name)
253        self.assertEqual(actual_names, expected_names)
254
255
256if __name__ == '__main__':
257    unittest.main(verbosity=2)
258