1# Test the functions and main class method of paragraph.py
2import unittest
3from idlelib import paragraph as fp
4from idlelib.editor import EditorWindow
5from tkinter import Tk, Text
6from test.support import requires
7
8
9class Is_Get_Test(unittest.TestCase):
10    """Test the is_ and get_ functions"""
11    test_comment = '# This is a comment'
12    test_nocomment = 'This is not a comment'
13    trailingws_comment = '# This is a comment   '
14    leadingws_comment = '    # This is a comment'
15    leadingws_nocomment = '    This is not a comment'
16
17    def test_is_all_white(self):
18        self.assertTrue(fp.is_all_white(''))
19        self.assertTrue(fp.is_all_white('\t\n\r\f\v'))
20        self.assertFalse(fp.is_all_white(self.test_comment))
21
22    def test_get_indent(self):
23        Equal = self.assertEqual
24        Equal(fp.get_indent(self.test_comment), '')
25        Equal(fp.get_indent(self.trailingws_comment), '')
26        Equal(fp.get_indent(self.leadingws_comment), '    ')
27        Equal(fp.get_indent(self.leadingws_nocomment), '    ')
28
29    def test_get_comment_header(self):
30        Equal = self.assertEqual
31        # Test comment strings
32        Equal(fp.get_comment_header(self.test_comment), '#')
33        Equal(fp.get_comment_header(self.trailingws_comment), '#')
34        Equal(fp.get_comment_header(self.leadingws_comment), '    #')
35        # Test non-comment strings
36        Equal(fp.get_comment_header(self.leadingws_nocomment), '    ')
37        Equal(fp.get_comment_header(self.test_nocomment), '')
38
39
40class FindTest(unittest.TestCase):
41    """Test the find_paragraph function in paragraph module.
42
43    Using the runcase() function, find_paragraph() is called with 'mark' set at
44    multiple indexes before and inside the test paragraph.
45
46    It appears that code with the same indentation as a quoted string is grouped
47    as part of the same paragraph, which is probably incorrect behavior.
48    """
49
50    @classmethod
51    def setUpClass(cls):
52        from idlelib.idle_test.mock_tk import Text
53        cls.text = Text()
54
55    def runcase(self, inserttext, stopline, expected):
56        # Check that find_paragraph returns the expected paragraph when
57        # the mark index is set to beginning, middle, end of each line
58        # up to but not including the stop line
59        text = self.text
60        text.insert('1.0', inserttext)
61        for line in range(1, stopline):
62            linelength = int(text.index("%d.end" % line).split('.')[1])
63            for col in (0, linelength//2, linelength):
64                tempindex = "%d.%d" % (line, col)
65                self.assertEqual(fp.find_paragraph(text, tempindex), expected)
66        text.delete('1.0', 'end')
67
68    def test_find_comment(self):
69        comment = (
70            "# Comment block with no blank lines before\n"
71            "# Comment line\n"
72            "\n")
73        self.runcase(comment, 3, ('1.0', '3.0', '#', comment[0:58]))
74
75        comment = (
76            "\n"
77            "# Comment block with whitespace line before and after\n"
78            "# Comment line\n"
79            "\n")
80        self.runcase(comment, 4, ('2.0', '4.0', '#', comment[1:70]))
81
82        comment = (
83            "\n"
84            "    # Indented comment block with whitespace before and after\n"
85            "    # Comment line\n"
86            "\n")
87        self.runcase(comment, 4, ('2.0', '4.0', '    #', comment[1:82]))
88
89        comment = (
90            "\n"
91            "# Single line comment\n"
92            "\n")
93        self.runcase(comment, 3, ('2.0', '3.0', '#', comment[1:23]))
94
95        comment = (
96            "\n"
97            "    # Single line comment with leading whitespace\n"
98            "\n")
99        self.runcase(comment, 3, ('2.0', '3.0', '    #', comment[1:51]))
100
101        comment = (
102            "\n"
103            "# Comment immediately followed by code\n"
104            "x = 42\n"
105            "\n")
106        self.runcase(comment, 3, ('2.0', '3.0', '#', comment[1:40]))
107
108        comment = (
109            "\n"
110            "    # Indented comment immediately followed by code\n"
111            "x = 42\n"
112            "\n")
113        self.runcase(comment, 3, ('2.0', '3.0', '    #', comment[1:53]))
114
115        comment = (
116            "\n"
117            "# Comment immediately followed by indented code\n"
118            "    x = 42\n"
119            "\n")
120        self.runcase(comment, 3, ('2.0', '3.0', '#', comment[1:49]))
121
122    def test_find_paragraph(self):
123        teststring = (
124            '"""String with no blank lines before\n'
125            'String line\n'
126            '"""\n'
127            '\n')
128        self.runcase(teststring, 4, ('1.0', '4.0', '', teststring[0:53]))
129
130        teststring = (
131            "\n"
132            '"""String with whitespace line before and after\n'
133            'String line.\n'
134            '"""\n'
135            '\n')
136        self.runcase(teststring, 5, ('2.0', '5.0', '', teststring[1:66]))
137
138        teststring = (
139            '\n'
140            '    """Indented string with whitespace before and after\n'
141            '    Comment string.\n'
142            '    """\n'
143            '\n')
144        self.runcase(teststring, 5, ('2.0', '5.0', '    ', teststring[1:85]))
145
146        teststring = (
147            '\n'
148            '"""Single line string."""\n'
149            '\n')
150        self.runcase(teststring, 3, ('2.0', '3.0', '', teststring[1:27]))
151
152        teststring = (
153            '\n'
154            '    """Single line string with leading whitespace."""\n'
155            '\n')
156        self.runcase(teststring, 3, ('2.0', '3.0', '    ', teststring[1:55]))
157
158
159class ReformatFunctionTest(unittest.TestCase):
160    """Test the reformat_paragraph function without the editor window."""
161
162    def test_reformat_paragraph(self):
163        Equal = self.assertEqual
164        reform = fp.reformat_paragraph
165        hw = "O hello world"
166        Equal(reform(' ', 1), ' ')
167        Equal(reform("Hello    world", 20), "Hello  world")
168
169        # Test without leading newline
170        Equal(reform(hw, 1), "O\nhello\nworld")
171        Equal(reform(hw, 6), "O\nhello\nworld")
172        Equal(reform(hw, 7), "O hello\nworld")
173        Equal(reform(hw, 12), "O hello\nworld")
174        Equal(reform(hw, 13), "O hello world")
175
176        # Test with leading newline
177        hw = "\nO hello world"
178        Equal(reform(hw, 1), "\nO\nhello\nworld")
179        Equal(reform(hw, 6), "\nO\nhello\nworld")
180        Equal(reform(hw, 7), "\nO hello\nworld")
181        Equal(reform(hw, 12), "\nO hello\nworld")
182        Equal(reform(hw, 13), "\nO hello world")
183
184
185class ReformatCommentTest(unittest.TestCase):
186    """Test the reformat_comment function without the editor window."""
187
188    def test_reformat_comment(self):
189        Equal = self.assertEqual
190
191        # reformat_comment formats to a minimum of 20 characters
192        test_string = (
193            "    \"\"\"this is a test of a reformat for a triple quoted string"
194            " will it reformat to less than 70 characters for me?\"\"\"")
195        result = fp.reformat_comment(test_string, 70, "    ")
196        expected = (
197            "    \"\"\"this is a test of a reformat for a triple quoted string will it\n"
198            "    reformat to less than 70 characters for me?\"\"\"")
199        Equal(result, expected)
200
201        test_comment = (
202            "# this is a test of a reformat for a triple quoted string will "
203            "it reformat to less than 70 characters for me?")
204        result = fp.reformat_comment(test_comment, 70, "#")
205        expected = (
206            "# this is a test of a reformat for a triple quoted string will it\n"
207            "# reformat to less than 70 characters for me?")
208        Equal(result, expected)
209
210
211class FormatClassTest(unittest.TestCase):
212    def test_init_close(self):
213        instance = fp.FormatParagraph('editor')
214        self.assertEqual(instance.editwin, 'editor')
215        instance.close()
216        self.assertEqual(instance.editwin, None)
217
218
219# For testing format_paragraph_event, Initialize FormatParagraph with
220# a mock Editor with .text and  .get_selection_indices.  The text must
221# be a Text wrapper that adds two methods
222
223# A real EditorWindow creates unneeded, time-consuming baggage and
224# sometimes emits shutdown warnings like this:
225# "warning: callback failed in WindowList <class '_tkinter.TclError'>
226# : invalid command name ".55131368.windows".
227# Calling EditorWindow._close in tearDownClass prevents this but causes
228# other problems (windows left open).
229
230class TextWrapper:
231    def __init__(self, master):
232        self.text = Text(master=master)
233    def __getattr__(self, name):
234        return getattr(self.text, name)
235    def undo_block_start(self): pass
236    def undo_block_stop(self): pass
237
238class Editor:
239    def __init__(self, root):
240        self.text = TextWrapper(root)
241    get_selection_indices = EditorWindow. get_selection_indices
242
243class FormatEventTest(unittest.TestCase):
244    """Test the formatting of text inside a Text widget.
245
246    This is done with FormatParagraph.format.paragraph_event,
247    which calls functions in the module as appropriate.
248    """
249    test_string = (
250        "    '''this is a test of a reformat for a triple "
251        "quoted string will it reformat to less than 70 "
252        "characters for me?'''\n")
253    multiline_test_string = (
254        "    '''The first line is under the max width.\n"
255        "    The second line's length is way over the max width. It goes "
256        "on and on until it is over 100 characters long.\n"
257        "    Same thing with the third line. It is also way over the max "
258        "width, but FormatParagraph will fix it.\n"
259        "    '''\n")
260    multiline_test_comment = (
261        "# The first line is under the max width.\n"
262        "# The second line's length is way over the max width. It goes on "
263        "and on until it is over 100 characters long.\n"
264        "# Same thing with the third line. It is also way over the max "
265        "width, but FormatParagraph will fix it.\n"
266        "# The fourth line is short like the first line.")
267
268    @classmethod
269    def setUpClass(cls):
270        requires('gui')
271        cls.root = Tk()
272        editor = Editor(root=cls.root)
273        cls.text = editor.text.text  # Test code does not need the wrapper.
274        cls.formatter = fp.FormatParagraph(editor).format_paragraph_event
275        # Sets the insert mark just after the re-wrapped and inserted  text.
276
277    @classmethod
278    def tearDownClass(cls):
279        del cls.text, cls.formatter
280        cls.root.destroy()
281        del cls.root
282
283    def test_short_line(self):
284        self.text.insert('1.0', "Short line\n")
285        self.formatter("Dummy")
286        self.assertEqual(self.text.get('1.0', 'insert'), "Short line\n" )
287        self.text.delete('1.0', 'end')
288
289    def test_long_line(self):
290        text = self.text
291
292        # Set cursor ('insert' mark) to '1.0', within text.
293        text.insert('1.0', self.test_string)
294        text.mark_set('insert', '1.0')
295        self.formatter('ParameterDoesNothing', limit=70)
296        result = text.get('1.0', 'insert')
297        # find function includes \n
298        expected = (
299"    '''this is a test of a reformat for a triple quoted string will it\n"
300"    reformat to less than 70 characters for me?'''\n")  # yes
301        self.assertEqual(result, expected)
302        text.delete('1.0', 'end')
303
304        # Select from 1.11 to line end.
305        text.insert('1.0', self.test_string)
306        text.tag_add('sel', '1.11', '1.end')
307        self.formatter('ParameterDoesNothing', limit=70)
308        result = text.get('1.0', 'insert')
309        # selection excludes \n
310        expected = (
311"    '''this is a test of a reformat for a triple quoted string will it reformat\n"
312" to less than 70 characters for me?'''")  # no
313        self.assertEqual(result, expected)
314        text.delete('1.0', 'end')
315
316    def test_multiple_lines(self):
317        text = self.text
318        #  Select 2 long lines.
319        text.insert('1.0', self.multiline_test_string)
320        text.tag_add('sel', '2.0', '4.0')
321        self.formatter('ParameterDoesNothing', limit=70)
322        result = text.get('2.0', 'insert')
323        expected = (
324"    The second line's length is way over the max width. It goes on and\n"
325"    on until it is over 100 characters long. Same thing with the third\n"
326"    line. It is also way over the max width, but FormatParagraph will\n"
327"    fix it.\n")
328        self.assertEqual(result, expected)
329        text.delete('1.0', 'end')
330
331    def test_comment_block(self):
332        text = self.text
333
334        # Set cursor ('insert') to '1.0', within block.
335        text.insert('1.0', self.multiline_test_comment)
336        self.formatter('ParameterDoesNothing', limit=70)
337        result = text.get('1.0', 'insert')
338        expected = (
339"# The first line is under the max width. The second line's length is\n"
340"# way over the max width. It goes on and on until it is over 100\n"
341"# characters long. Same thing with the third line. It is also way over\n"
342"# the max width, but FormatParagraph will fix it. The fourth line is\n"
343"# short like the first line.\n")
344        self.assertEqual(result, expected)
345        text.delete('1.0', 'end')
346
347        # Select line 2, verify line 1 unaffected.
348        text.insert('1.0', self.multiline_test_comment)
349        text.tag_add('sel', '2.0', '3.0')
350        self.formatter('ParameterDoesNothing', limit=70)
351        result = text.get('1.0', 'insert')
352        expected = (
353"# The first line is under the max width.\n"
354"# The second line's length is way over the max width. It goes on and\n"
355"# on until it is over 100 characters long.\n")
356        self.assertEqual(result, expected)
357        text.delete('1.0', 'end')
358
359# The following block worked with EditorWindow but fails with the mock.
360# Lines 2 and 3 get pasted together even though the previous block left
361# the previous line alone. More investigation is needed.
362##        # Select lines 3 and 4
363##        text.insert('1.0', self.multiline_test_comment)
364##        text.tag_add('sel', '3.0', '5.0')
365##        self.formatter('ParameterDoesNothing')
366##        result = text.get('3.0', 'insert')
367##        expected = (
368##"# Same thing with the third line. It is also way over the max width,\n"
369##"# but FormatParagraph will fix it. The fourth line is short like the\n"
370##"# first line.\n")
371##        self.assertEqual(result, expected)
372##        text.delete('1.0', 'end')
373
374
375if __name__ == '__main__':
376    unittest.main(verbosity=2, exit=2)
377