1"Test editor, coverage 53%."
2
3from idlelib import editor
4import unittest
5from collections import namedtuple
6from test.support import requires
7from tkinter import Tk, Text
8
9Editor = editor.EditorWindow
10
11
12class EditorWindowTest(unittest.TestCase):
13
14    @classmethod
15    def setUpClass(cls):
16        requires('gui')
17        cls.root = Tk()
18        cls.root.withdraw()
19
20    @classmethod
21    def tearDownClass(cls):
22        cls.root.update_idletasks()
23        for id in cls.root.tk.call('after', 'info'):
24            cls.root.after_cancel(id)
25        cls.root.destroy()
26        del cls.root
27
28    def test_init(self):
29        e = Editor(root=self.root)
30        self.assertEqual(e.root, self.root)
31        e._close()
32
33
34class GetLineIndentTest(unittest.TestCase):
35    def test_empty_lines(self):
36        for tabwidth in [1, 2, 4, 6, 8]:
37            for line in ['', '\n']:
38                with self.subTest(line=line, tabwidth=tabwidth):
39                    self.assertEqual(
40                        editor.get_line_indent(line, tabwidth=tabwidth),
41                        (0, 0),
42                    )
43
44    def test_tabwidth_4(self):
45        #        (line, (raw, effective))
46        tests = (('no spaces', (0, 0)),
47                 # Internal space isn't counted.
48                 ('    space test', (4, 4)),
49                 ('\ttab test', (1, 4)),
50                 ('\t\tdouble tabs test', (2, 8)),
51                 # Different results when mixing tabs and spaces.
52                 ('    \tmixed test', (5, 8)),
53                 ('  \t  mixed test', (5, 6)),
54                 ('\t    mixed test', (5, 8)),
55                 # Spaces not divisible by tabwidth.
56                 ('  \tmixed test', (3, 4)),
57                 (' \t mixed test', (3, 5)),
58                 ('\t  mixed test', (3, 6)),
59                 # Only checks spaces and tabs.
60                 ('\nnewline test', (0, 0)))
61
62        for line, expected in tests:
63            with self.subTest(line=line):
64                self.assertEqual(
65                    editor.get_line_indent(line, tabwidth=4),
66                    expected,
67                )
68
69    def test_tabwidth_8(self):
70        #        (line, (raw, effective))
71        tests = (('no spaces', (0, 0)),
72                 # Internal space isn't counted.
73                 ('        space test', (8, 8)),
74                 ('\ttab test', (1, 8)),
75                 ('\t\tdouble tabs test', (2, 16)),
76                 # Different results when mixing tabs and spaces.
77                 ('        \tmixed test', (9, 16)),
78                 ('      \t  mixed test', (9, 10)),
79                 ('\t        mixed test', (9, 16)),
80                 # Spaces not divisible by tabwidth.
81                 ('  \tmixed test', (3, 8)),
82                 (' \t mixed test', (3, 9)),
83                 ('\t  mixed test', (3, 10)),
84                 # Only checks spaces and tabs.
85                 ('\nnewline test', (0, 0)))
86
87        for line, expected in tests:
88            with self.subTest(line=line):
89                self.assertEqual(
90                    editor.get_line_indent(line, tabwidth=8),
91                    expected,
92                )
93
94
95def insert(text, string):
96    text.delete('1.0', 'end')
97    text.insert('end', string)
98    text.update()  # Force update for colorizer to finish.
99
100
101class IndentAndNewlineTest(unittest.TestCase):
102
103    @classmethod
104    def setUpClass(cls):
105        requires('gui')
106        cls.root = Tk()
107        cls.root.withdraw()
108        cls.window = Editor(root=cls.root)
109        cls.window.indentwidth = 2
110        cls.window.tabwidth = 2
111
112    @classmethod
113    def tearDownClass(cls):
114        cls.window._close()
115        del cls.window
116        cls.root.update_idletasks()
117        for id in cls.root.tk.call('after', 'info'):
118            cls.root.after_cancel(id)
119        cls.root.destroy()
120        del cls.root
121
122    def test_indent_and_newline_event(self):
123        eq = self.assertEqual
124        w = self.window
125        text = w.text
126        get = text.get
127        nl = w.newline_and_indent_event
128
129        TestInfo = namedtuple('Tests', ['label', 'text', 'expected', 'mark'])
130
131        tests = (TestInfo('Empty line inserts with no indent.',
132                          '  \n  def __init__(self):',
133                          '\n  \n  def __init__(self):\n',
134                          '1.end'),
135                 TestInfo('Inside bracket before space, deletes space.',
136                          '  def f1(self, a, b):',
137                          '  def f1(self,\n         a, b):\n',
138                          '1.14'),
139                 TestInfo('Inside bracket after space, deletes space.',
140                          '  def f1(self, a, b):',
141                          '  def f1(self,\n         a, b):\n',
142                          '1.15'),
143                 TestInfo('Inside string with one line - no indent.',
144                          '  """Docstring."""',
145                          '  """Docstring.\n"""\n',
146                          '1.15'),
147                 TestInfo('Inside string with more than one line.',
148                          '  """Docstring.\n  Docstring Line 2"""',
149                          '  """Docstring.\n  Docstring Line 2\n  """\n',
150                          '2.18'),
151                 TestInfo('Backslash with one line.',
152                          'a =\\',
153                          'a =\\\n  \n',
154                          '1.end'),
155                 TestInfo('Backslash with more than one line.',
156                          'a =\\\n          multiline\\',
157                          'a =\\\n          multiline\\\n          \n',
158                          '2.end'),
159                 TestInfo('Block opener - indents +1 level.',
160                          '  def f1(self):\n    pass',
161                          '  def f1(self):\n    \n    pass\n',
162                          '1.end'),
163                 TestInfo('Block closer - dedents -1 level.',
164                          '  def f1(self):\n    pass',
165                          '  def f1(self):\n    pass\n  \n',
166                          '2.end'),
167                 )
168
169        for test in tests:
170            with self.subTest(label=test.label):
171                insert(text, test.text)
172                text.mark_set('insert', test.mark)
173                nl(event=None)
174                eq(get('1.0', 'end'), test.expected)
175
176        # Selected text.
177        insert(text, '  def f1(self, a, b):\n    return a + b')
178        text.tag_add('sel', '1.17', '1.end')
179        nl(None)
180        # Deletes selected text before adding new line.
181        eq(get('1.0', 'end'), '  def f1(self, a,\n         \n    return a + b\n')
182
183
184class IndentSearcherTest(unittest.TestCase):
185
186    @classmethod
187    def setUpClass(cls):
188        requires('gui')
189        cls.root = Tk()
190        cls.root.withdraw()
191        cls.text = Text(cls.root)
192
193    @classmethod
194    def tearDownClass(cls):
195        cls.root.destroy()
196        del cls.root
197
198    def test_searcher(self):
199        text = self.text
200        searcher = (self.text)
201        test_info = (# text, (block, indent))
202                     ("", (None, None)),
203                     ("[1,", (None, None)),  # TokenError
204                     ("if 1:\n", ('if 1:\n', None)),
205                     ("if 1:\n  2\n  3\n", ('if 1:\n', '  2\n')),
206                     )
207        for code, expected_pair in test_info:
208            with self.subTest(code=code):
209                insert(text, code)
210                actual_pair = editor.IndentSearcher(text).run()
211                self.assertEqual(actual_pair, expected_pair)
212
213
214class RMenuTest(unittest.TestCase):
215
216    @classmethod
217    def setUpClass(cls):
218        requires('gui')
219        cls.root = Tk()
220        cls.root.withdraw()
221        cls.window = Editor(root=cls.root)
222
223    @classmethod
224    def tearDownClass(cls):
225        cls.window._close()
226        del cls.window
227        cls.root.update_idletasks()
228        for id in cls.root.tk.call('after', 'info'):
229            cls.root.after_cancel(id)
230        cls.root.destroy()
231        del cls.root
232
233    class DummyRMenu:
234        def tk_popup(x, y): pass
235
236    def test_rclick(self):
237        pass
238
239
240if __name__ == '__main__':
241    unittest.main(verbosity=2)
242