(root)/
Python-3.12.0/
Lib/
idlelib/
idle_test/
test_format.py
       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)