1 "Test format, coverage 99%."
2
3 from idlelib import format as ft
4 import unittest
5 from unittest import mock
6 from test.support import requires
7 from tkinter import Tk, Text
8 from idlelib.editor import EditorWindow
9 from idlelib.idle_test.mock_idle import Editor as MockEditor
10
11
12 class ESC[4;38;5;81mIs_Get_Test(ESC[4;38;5;149munittestESC[4;38;5;149m.ESC[4;38;5;149mTestCase):
13 """Test the is_ and get_ functions"""
14 test_comment = '# This is a comment'
15 test_nocomment = 'This is not a comment'
16 trailingws_comment = '# This is a comment '
17 leadingws_comment = ' # This is a comment'
18 leadingws_nocomment = ' This is not a comment'
19
20 def test_is_all_white(self):
21 self.assertTrue(ft.is_all_white(''))
22 self.assertTrue(ft.is_all_white('\t\n\r\f\v'))
23 self.assertFalse(ft.is_all_white(self.test_comment))
24
25 def test_get_indent(self):
26 Equal = self.assertEqual
27 Equal(ft.get_indent(self.test_comment), '')
28 Equal(ft.get_indent(self.trailingws_comment), '')
29 Equal(ft.get_indent(self.leadingws_comment), ' ')
30 Equal(ft.get_indent(self.leadingws_nocomment), ' ')
31
32 def test_get_comment_header(self):
33 Equal = self.assertEqual
34 # Test comment strings
35 Equal(ft.get_comment_header(self.test_comment), '#')
36 Equal(ft.get_comment_header(self.trailingws_comment), '#')
37 Equal(ft.get_comment_header(self.leadingws_comment), ' #')
38 # Test non-comment strings
39 Equal(ft.get_comment_header(self.leadingws_nocomment), ' ')
40 Equal(ft.get_comment_header(self.test_nocomment), '')
41
42
43 class ESC[4;38;5;81mFindTest(ESC[4;38;5;149munittestESC[4;38;5;149m.ESC[4;38;5;149mTestCase):
44 """Test the find_paragraph function in paragraph module.
45
46 Using the runcase() function, find_paragraph() is called with 'mark' set at
47 multiple indexes before and inside the test paragraph.
48
49 It appears that code with the same indentation as a quoted string is grouped
50 as part of the same paragraph, which is probably incorrect behavior.
51 """
52
53 @classmethod
54 def setUpClass(cls):
55 from idlelib.idle_test.mock_tk import Text
56 cls.text = Text()
57
58 def runcase(self, inserttext, stopline, expected):
59 # Check that find_paragraph returns the expected paragraph when
60 # the mark index is set to beginning, middle, end of each line
61 # up to but not including the stop line
62 text = self.text
63 text.insert('1.0', inserttext)
64 for line in range(1, stopline):
65 linelength = int(text.index("%d.end" % line).split('.')[1])
66 for col in (0, linelength//2, linelength):
67 tempindex = "%d.%d" % (line, col)
68 self.assertEqual(ft.find_paragraph(text, tempindex), expected)
69 text.delete('1.0', 'end')
70
71 def test_find_comment(self):
72 comment = (
73 "# Comment block with no blank lines before\n"
74 "# Comment line\n"
75 "\n")
76 self.runcase(comment, 3, ('1.0', '3.0', '#', comment[0:58]))
77
78 comment = (
79 "\n"
80 "# Comment block with whitespace line before and after\n"
81 "# Comment line\n"
82 "\n")
83 self.runcase(comment, 4, ('2.0', '4.0', '#', comment[1:70]))
84
85 comment = (
86 "\n"
87 " # Indented comment block with whitespace before and after\n"
88 " # Comment line\n"
89 "\n")
90 self.runcase(comment, 4, ('2.0', '4.0', ' #', comment[1:82]))
91
92 comment = (
93 "\n"
94 "# Single line comment\n"
95 "\n")
96 self.runcase(comment, 3, ('2.0', '3.0', '#', comment[1:23]))
97
98 comment = (
99 "\n"
100 " # Single line comment with leading whitespace\n"
101 "\n")
102 self.runcase(comment, 3, ('2.0', '3.0', ' #', comment[1:51]))
103
104 comment = (
105 "\n"
106 "# Comment immediately followed by code\n"
107 "x = 42\n"
108 "\n")
109 self.runcase(comment, 3, ('2.0', '3.0', '#', comment[1:40]))
110
111 comment = (
112 "\n"
113 " # Indented comment immediately followed by code\n"
114 "x = 42\n"
115 "\n")
116 self.runcase(comment, 3, ('2.0', '3.0', ' #', comment[1:53]))
117
118 comment = (
119 "\n"
120 "# Comment immediately followed by indented code\n"
121 " x = 42\n"
122 "\n")
123 self.runcase(comment, 3, ('2.0', '3.0', '#', comment[1:49]))
124
125 def test_find_paragraph(self):
126 teststring = (
127 '"""String with no blank lines before\n'
128 'String line\n'
129 '"""\n'
130 '\n')
131 self.runcase(teststring, 4, ('1.0', '4.0', '', teststring[0:53]))
132
133 teststring = (
134 "\n"
135 '"""String with whitespace line before and after\n'
136 'String line.\n'
137 '"""\n'
138 '\n')
139 self.runcase(teststring, 5, ('2.0', '5.0', '', teststring[1:66]))
140
141 teststring = (
142 '\n'
143 ' """Indented string with whitespace before and after\n'
144 ' Comment string.\n'
145 ' """\n'
146 '\n')
147 self.runcase(teststring, 5, ('2.0', '5.0', ' ', teststring[1:85]))
148
149 teststring = (
150 '\n'
151 '"""Single line string."""\n'
152 '\n')
153 self.runcase(teststring, 3, ('2.0', '3.0', '', teststring[1:27]))
154
155 teststring = (
156 '\n'
157 ' """Single line string with leading whitespace."""\n'
158 '\n')
159 self.runcase(teststring, 3, ('2.0', '3.0', ' ', teststring[1:55]))
160
161
162 class ESC[4;38;5;81mReformatFunctionTest(ESC[4;38;5;149munittestESC[4;38;5;149m.ESC[4;38;5;149mTestCase):
163 """Test the reformat_paragraph function without the editor window."""
164
165 def test_reformat_paragraph(self):
166 Equal = self.assertEqual
167 reform = ft.reformat_paragraph
168 hw = "O hello world"
169 Equal(reform(' ', 1), ' ')
170 Equal(reform("Hello world", 20), "Hello world")
171
172 # Test without leading newline
173 Equal(reform(hw, 1), "O\nhello\nworld")
174 Equal(reform(hw, 6), "O\nhello\nworld")
175 Equal(reform(hw, 7), "O hello\nworld")
176 Equal(reform(hw, 12), "O hello\nworld")
177 Equal(reform(hw, 13), "O hello world")
178
179 # Test with leading newline
180 hw = "\nO hello world"
181 Equal(reform(hw, 1), "\nO\nhello\nworld")
182 Equal(reform(hw, 6), "\nO\nhello\nworld")
183 Equal(reform(hw, 7), "\nO hello\nworld")
184 Equal(reform(hw, 12), "\nO hello\nworld")
185 Equal(reform(hw, 13), "\nO hello world")
186
187
188 class ESC[4;38;5;81mReformatCommentTest(ESC[4;38;5;149munittestESC[4;38;5;149m.ESC[4;38;5;149mTestCase):
189 """Test the reformat_comment function without the editor window."""
190
191 def test_reformat_comment(self):
192 Equal = self.assertEqual
193
194 # reformat_comment formats to a minimum of 20 characters
195 test_string = (
196 " \"\"\"this is a test of a reformat for a triple quoted string"
197 " will it reformat to less than 70 characters for me?\"\"\"")
198 result = ft.reformat_comment(test_string, 70, " ")
199 expected = (
200 " \"\"\"this is a test of a reformat for a triple quoted string will it\n"
201 " reformat to less than 70 characters for me?\"\"\"")
202 Equal(result, expected)
203
204 test_comment = (
205 "# this is a test of a reformat for a triple quoted string will "
206 "it reformat to less than 70 characters for me?")
207 result = ft.reformat_comment(test_comment, 70, "#")
208 expected = (
209 "# this is a test of a reformat for a triple quoted string will it\n"
210 "# reformat to less than 70 characters for me?")
211 Equal(result, expected)
212
213
214 class ESC[4;38;5;81mFormatClassTest(ESC[4;38;5;149munittestESC[4;38;5;149m.ESC[4;38;5;149mTestCase):
215 def test_init_close(self):
216 instance = ft.FormatParagraph('editor')
217 self.assertEqual(instance.editwin, 'editor')
218 instance.close()
219 self.assertEqual(instance.editwin, None)
220
221
222 # For testing format_paragraph_event, Initialize FormatParagraph with
223 # a mock Editor with .text and .get_selection_indices. The text must
224 # be a Text wrapper that adds two methods
225
226 # A real EditorWindow creates unneeded, time-consuming baggage and
227 # sometimes emits shutdown warnings like this:
228 # "warning: callback failed in WindowList <class '_tkinter.TclError'>
229 # : invalid command name ".55131368.windows".
230 # Calling EditorWindow._close in tearDownClass prevents this but causes
231 # other problems (windows left open).
232
233 class ESC[4;38;5;81mTextWrapper:
234 def __init__(self, master):
235 self.text = Text(master=master)
236 def __getattr__(self, name):
237 return getattr(self.text, name)
238 def undo_block_start(self): pass
239 def undo_block_stop(self): pass
240
241 class ESC[4;38;5;81mEditor:
242 def __init__(self, root):
243 self.text = TextWrapper(root)
244 get_selection_indices = EditorWindow. get_selection_indices
245
246 class ESC[4;38;5;81mFormatEventTest(ESC[4;38;5;149munittestESC[4;38;5;149m.ESC[4;38;5;149mTestCase):
247 """Test the formatting of text inside a Text widget.
248
249 This is done with FormatParagraph.format.paragraph_event,
250 which calls functions in the module as appropriate.
251 """
252 test_string = (
253 " '''this is a test of a reformat for a triple "
254 "quoted string will it reformat to less than 70 "
255 "characters for me?'''\n")
256 multiline_test_string = (
257 " '''The first line is under the max width.\n"
258 " The second line's length is way over the max width. It goes "
259 "on and on until it is over 100 characters long.\n"
260 " Same thing with the third line. It is also way over the max "
261 "width, but FormatParagraph will fix it.\n"
262 " '''\n")
263 multiline_test_comment = (
264 "# The first line is under the max width.\n"
265 "# The second line's length is way over the max width. It goes on "
266 "and on until it is over 100 characters long.\n"
267 "# Same thing with the third line. It is also way over the max "
268 "width, but FormatParagraph will fix it.\n"
269 "# The fourth line is short like the first line.")
270
271 @classmethod
272 def setUpClass(cls):
273 requires('gui')
274 cls.root = Tk()
275 cls.root.withdraw()
276 editor = Editor(root=cls.root)
277 cls.text = editor.text.text # Test code does not need the wrapper.
278 cls.formatter = ft.FormatParagraph(editor).format_paragraph_event
279 # Sets the insert mark just after the re-wrapped and inserted text.
280
281 @classmethod
282 def tearDownClass(cls):
283 del cls.text, cls.formatter
284 cls.root.update_idletasks()
285 cls.root.destroy()
286 del cls.root
287
288 def test_short_line(self):
289 self.text.insert('1.0', "Short line\n")
290 self.formatter("Dummy")
291 self.assertEqual(self.text.get('1.0', 'insert'), "Short line\n" )
292 self.text.delete('1.0', 'end')
293
294 def test_long_line(self):
295 text = self.text
296
297 # Set cursor ('insert' mark) to '1.0', within text.
298 text.insert('1.0', self.test_string)
299 text.mark_set('insert', '1.0')
300 self.formatter('ParameterDoesNothing', limit=70)
301 result = text.get('1.0', 'insert')
302 # find function includes \n
303 expected = (
304 " '''this is a test of a reformat for a triple quoted string will it\n"
305 " reformat to less than 70 characters for me?'''\n") # yes
306 self.assertEqual(result, expected)
307 text.delete('1.0', 'end')
308
309 # Select from 1.11 to line end.
310 text.insert('1.0', self.test_string)
311 text.tag_add('sel', '1.11', '1.end')
312 self.formatter('ParameterDoesNothing', limit=70)
313 result = text.get('1.0', 'insert')
314 # selection excludes \n
315 expected = (
316 " '''this is a test of a reformat for a triple quoted string will it reformat\n"
317 " to less than 70 characters for me?'''") # no
318 self.assertEqual(result, expected)
319 text.delete('1.0', 'end')
320
321 def test_multiple_lines(self):
322 text = self.text
323 # Select 2 long lines.
324 text.insert('1.0', self.multiline_test_string)
325 text.tag_add('sel', '2.0', '4.0')
326 self.formatter('ParameterDoesNothing', limit=70)
327 result = text.get('2.0', 'insert')
328 expected = (
329 " The second line's length is way over the max width. It goes on and\n"
330 " on until it is over 100 characters long. Same thing with the third\n"
331 " line. It is also way over the max width, but FormatParagraph will\n"
332 " fix it.\n")
333 self.assertEqual(result, expected)
334 text.delete('1.0', 'end')
335
336 def test_comment_block(self):
337 text = self.text
338
339 # Set cursor ('insert') to '1.0', within block.
340 text.insert('1.0', self.multiline_test_comment)
341 self.formatter('ParameterDoesNothing', limit=70)
342 result = text.get('1.0', 'insert')
343 expected = (
344 "# The first line is under the max width. The second line's length is\n"
345 "# way over the max width. It goes on and on until it is over 100\n"
346 "# characters long. Same thing with the third line. It is also way over\n"
347 "# the max width, but FormatParagraph will fix it. The fourth line is\n"
348 "# short like the first line.\n")
349 self.assertEqual(result, expected)
350 text.delete('1.0', 'end')
351
352 # Select line 2, verify line 1 unaffected.
353 text.insert('1.0', self.multiline_test_comment)
354 text.tag_add('sel', '2.0', '3.0')
355 self.formatter('ParameterDoesNothing', limit=70)
356 result = text.get('1.0', 'insert')
357 expected = (
358 "# The first line is under the max width.\n"
359 "# The second line's length is way over the max width. It goes on and\n"
360 "# on until it is over 100 characters long.\n")
361 self.assertEqual(result, expected)
362 text.delete('1.0', 'end')
363
364 # The following block worked with EditorWindow but fails with the mock.
365 # Lines 2 and 3 get pasted together even though the previous block left
366 # the previous line alone. More investigation is needed.
367 ## # Select lines 3 and 4
368 ## text.insert('1.0', self.multiline_test_comment)
369 ## text.tag_add('sel', '3.0', '5.0')
370 ## self.formatter('ParameterDoesNothing')
371 ## result = text.get('3.0', 'insert')
372 ## expected = (
373 ##"# Same thing with the third line. It is also way over the max width,\n"
374 ##"# but FormatParagraph will fix it. The fourth line is short like the\n"
375 ##"# first line.\n")
376 ## self.assertEqual(result, expected)
377 ## text.delete('1.0', 'end')
378
379
380 class ESC[4;38;5;81mDummyEditwin:
381 def __init__(self, root, text):
382 self.root = root
383 self.text = text
384 self.indentwidth = 4
385 self.tabwidth = 4
386 self.usetabs = False
387 self.context_use_ps1 = True
388
389 _make_blanks = EditorWindow._make_blanks
390 get_selection_indices = EditorWindow.get_selection_indices
391
392
393 class ESC[4;38;5;81mFormatRegionTest(ESC[4;38;5;149munittestESC[4;38;5;149m.ESC[4;38;5;149mTestCase):
394
395 @classmethod
396 def setUpClass(cls):
397 requires('gui')
398 cls.root = Tk()
399 cls.root.withdraw()
400 cls.text = Text(cls.root)
401 cls.text.undo_block_start = mock.Mock()
402 cls.text.undo_block_stop = mock.Mock()
403 cls.editor = DummyEditwin(cls.root, cls.text)
404 cls.formatter = ft.FormatRegion(cls.editor)
405
406 @classmethod
407 def tearDownClass(cls):
408 del cls.text, cls.formatter, cls.editor
409 cls.root.update_idletasks()
410 cls.root.destroy()
411 del cls.root
412
413 def setUp(self):
414 self.text.insert('1.0', self.code_sample)
415
416 def tearDown(self):
417 self.text.delete('1.0', 'end')
418
419 code_sample = """\
420 # WS line needed for test.
421 class C1:
422 # Class comment.
423 def __init__(self, a, b):
424 self.a = a
425 self.b = b
426
427 def compare(self):
428 if a > b:
429 return a
430 elif a < b:
431 return b
432 else:
433 return None
434 """
435
436 def test_get_region(self):
437 get = self.formatter.get_region
438 text = self.text
439 eq = self.assertEqual
440
441 # Add selection.
442 text.tag_add('sel', '7.0', '10.0')
443 expected_lines = ['',
444 ' def compare(self):',
445 ' if a > b:',
446 '']
447 eq(get(), ('7.0', '10.0', '\n'.join(expected_lines), expected_lines))
448
449 # Remove selection.
450 text.tag_remove('sel', '1.0', 'end')
451 eq(get(), ('15.0', '16.0', '\n', ['', '']))
452
453 def test_set_region(self):
454 set_ = self.formatter.set_region
455 text = self.text
456 eq = self.assertEqual
457
458 save_bell = text.bell
459 text.bell = mock.Mock()
460 line6 = self.code_sample.splitlines()[5]
461 line10 = self.code_sample.splitlines()[9]
462
463 text.tag_add('sel', '6.0', '11.0')
464 head, tail, chars, lines = self.formatter.get_region()
465
466 # No changes.
467 set_(head, tail, chars, lines)
468 text.bell.assert_called_once()
469 eq(text.get('6.0', '11.0'), chars)
470 eq(text.get('sel.first', 'sel.last'), chars)
471 text.tag_remove('sel', '1.0', 'end')
472
473 # Alter selected lines by changing lines and adding a newline.
474 newstring = 'added line 1\n\n\n\n'
475 newlines = newstring.split('\n')
476 set_('7.0', '10.0', chars, newlines)
477 # Selection changed.
478 eq(text.get('sel.first', 'sel.last'), newstring)
479 # Additional line added, so last index is changed.
480 eq(text.get('7.0', '11.0'), newstring)
481 # Before and after lines unchanged.
482 eq(text.get('6.0', '7.0-1c'), line6)
483 eq(text.get('11.0', '12.0-1c'), line10)
484 text.tag_remove('sel', '1.0', 'end')
485
486 text.bell = save_bell
487
488 def test_indent_region_event(self):
489 indent = self.formatter.indent_region_event
490 text = self.text
491 eq = self.assertEqual
492
493 text.tag_add('sel', '7.0', '10.0')
494 indent()
495 # Blank lines aren't affected by indent.
496 eq(text.get('7.0', '10.0'), ('\n def compare(self):\n if a > b:\n'))
497
498 def test_dedent_region_event(self):
499 dedent = self.formatter.dedent_region_event
500 text = self.text
501 eq = self.assertEqual
502
503 text.tag_add('sel', '7.0', '10.0')
504 dedent()
505 # Blank lines aren't affected by dedent.
506 eq(text.get('7.0', '10.0'), ('\ndef compare(self):\n if a > b:\n'))
507
508 def test_comment_region_event(self):
509 comment = self.formatter.comment_region_event
510 text = self.text
511 eq = self.assertEqual
512
513 text.tag_add('sel', '7.0', '10.0')
514 comment()
515 eq(text.get('7.0', '10.0'), ('##\n## def compare(self):\n## if a > b:\n'))
516
517 def test_uncomment_region_event(self):
518 comment = self.formatter.comment_region_event
519 uncomment = self.formatter.uncomment_region_event
520 text = self.text
521 eq = self.assertEqual
522
523 text.tag_add('sel', '7.0', '10.0')
524 comment()
525 uncomment()
526 eq(text.get('7.0', '10.0'), ('\n def compare(self):\n if a > b:\n'))
527
528 # Only remove comments at the beginning of a line.
529 text.tag_remove('sel', '1.0', 'end')
530 text.tag_add('sel', '3.0', '4.0')
531 uncomment()
532 eq(text.get('3.0', '3.end'), (' # Class comment.'))
533
534 self.formatter.set_region('3.0', '4.0', '', ['# Class comment.', ''])
535 uncomment()
536 eq(text.get('3.0', '3.end'), (' Class comment.'))
537
538 @mock.patch.object(ft.FormatRegion, "_asktabwidth")
539 def test_tabify_region_event(self, _asktabwidth):
540 tabify = self.formatter.tabify_region_event
541 text = self.text
542 eq = self.assertEqual
543
544 text.tag_add('sel', '7.0', '10.0')
545 # No tabwidth selected.
546 _asktabwidth.return_value = None
547 self.assertIsNone(tabify())
548
549 _asktabwidth.return_value = 3
550 self.assertIsNotNone(tabify())
551 eq(text.get('7.0', '10.0'), ('\n\t def compare(self):\n\t\t if a > b:\n'))
552
553 @mock.patch.object(ft.FormatRegion, "_asktabwidth")
554 def test_untabify_region_event(self, _asktabwidth):
555 untabify = self.formatter.untabify_region_event
556 text = self.text
557 eq = self.assertEqual
558
559 text.tag_add('sel', '7.0', '10.0')
560 # No tabwidth selected.
561 _asktabwidth.return_value = None
562 self.assertIsNone(untabify())
563
564 _asktabwidth.return_value = 2
565 self.formatter.tabify_region_event()
566 _asktabwidth.return_value = 3
567 self.assertIsNotNone(untabify())
568 eq(text.get('7.0', '10.0'), ('\n def compare(self):\n if a > b:\n'))
569
570 @mock.patch.object(ft, "askinteger")
571 def test_ask_tabwidth(self, askinteger):
572 ask = self.formatter._asktabwidth
573 askinteger.return_value = 10
574 self.assertEqual(ask(), 10)
575
576
577 class ESC[4;38;5;81mIndentsTest(ESC[4;38;5;149munittestESC[4;38;5;149m.ESC[4;38;5;149mTestCase):
578
579 @mock.patch.object(ft, "askyesno")
580 def test_toggle_tabs(self, askyesno):
581 editor = DummyEditwin(None, None) # usetabs == False.
582 indents = ft.Indents(editor)
583 askyesno.return_value = True
584
585 indents.toggle_tabs_event(None)
586 self.assertEqual(editor.usetabs, True)
587 self.assertEqual(editor.indentwidth, 8)
588
589 indents.toggle_tabs_event(None)
590 self.assertEqual(editor.usetabs, False)
591 self.assertEqual(editor.indentwidth, 8)
592
593 @mock.patch.object(ft, "askinteger")
594 def test_change_indentwidth(self, askinteger):
595 editor = DummyEditwin(None, None) # indentwidth == 4.
596 indents = ft.Indents(editor)
597
598 askinteger.return_value = None
599 indents.change_indentwidth_event(None)
600 self.assertEqual(editor.indentwidth, 4)
601
602 askinteger.return_value = 3
603 indents.change_indentwidth_event(None)
604 self.assertEqual(editor.indentwidth, 3)
605
606 askinteger.return_value = 5
607 editor.usetabs = True
608 indents.change_indentwidth_event(None)
609 self.assertEqual(editor.indentwidth, 3)
610
611
612 class ESC[4;38;5;81mRstripTest(ESC[4;38;5;149munittestESC[4;38;5;149m.ESC[4;38;5;149mTestCase):
613
614 @classmethod
615 def setUpClass(cls):
616 requires('gui')
617 cls.root = Tk()
618 cls.root.withdraw()
619 cls.text = Text(cls.root)
620 cls.editor = MockEditor(text=cls.text)
621 cls.do_rstrip = ft.Rstrip(cls.editor).do_rstrip
622
623 @classmethod
624 def tearDownClass(cls):
625 del cls.text, cls.do_rstrip, cls.editor
626 cls.root.update_idletasks()
627 cls.root.destroy()
628 del cls.root
629
630 def tearDown(self):
631 self.text.delete('1.0', 'end-1c')
632
633 def test_rstrip_lines(self):
634 original = (
635 "Line with an ending tab \n"
636 "Line ending in 5 spaces \n"
637 "Linewithnospaces\n"
638 " indented line\n"
639 " indented line with trailing space \n"
640 " \n")
641 stripped = (
642 "Line with an ending tab\n"
643 "Line ending in 5 spaces\n"
644 "Linewithnospaces\n"
645 " indented line\n"
646 " indented line with trailing space\n")
647
648 self.text.insert('1.0', original)
649 self.do_rstrip()
650 self.assertEqual(self.text.get('1.0', 'insert'), stripped)
651
652 def test_rstrip_end(self):
653 text = self.text
654 for code in ('', '\n', '\n\n\n'):
655 with self.subTest(code=code):
656 text.insert('1.0', code)
657 self.do_rstrip()
658 self.assertEqual(text.get('1.0','end-1c'), '')
659 for code in ('a\n', 'a\n\n', 'a\n\n\n'):
660 with self.subTest(code=code):
661 text.delete('1.0', 'end-1c')
662 text.insert('1.0', code)
663 self.do_rstrip()
664 self.assertEqual(text.get('1.0','end-1c'), 'a\n')
665
666
667 if __name__ == '__main__':
668 unittest.main(verbosity=2, exit=2)