helper for tests create a root tk object requires gui root tk root withdraw def cleanuproot root updateidletasks root destroy testinstance addcleanupcleanuproot return root class countlinestestunittest testcase test with an empty string self assertequalcountlineswithwrapping 0 def testcountbeginswithemptylineself test with a string which ends with a newline self assertequalcountlineswithwrappingtextn 1 def testcountseverallinesself tests for the squeezer class def makemockeditorwindowself withtextwidgetfalse create an actual squeezer instance with a mock editorwindow if editorwindow is none editorwindow self makemockeditorwindow squeezer squeezereditorwindow return squeezer def maketextwidgetself rootnone if root is none root gettesttkrootself textwidget textroot textwidgetfont courier 10 textwidget marksetiomark 1 0 return textwidget def setidleconfoptionwithcleanupself configtype section option value prevval idleconf getoptionconfigtype section option idleconf setoptionconfigtype section option value self addcleanupidleconf setoption configtype section option prevval def testcountlinesself test the creation of squeezer instances editwin self makemockeditorwindow squeezer self makesqueezerinstanceeditwin self assertissqueezer editwin editwin self assertequalsqueezer expandingbuttons def testwritenotagsself test squeezer s overriding of the editorwindow s write method for text in text long text 1000 manylinesn 100 editwin self makemockeditorwindow editwin write returnvalue sentinelvalue origwrite editwin write squeezer self makesqueezerinstanceeditwin self assertequalsqueezer editwin writetext stderr sentinelvalue self assertequalorigwrite callcount 1 origwrite assertcalledwithtext stderr self assertequallensqueezer expandingbuttons 0 def testwritestdoutself test that the autosqueezing creates an expandingbutton properly editwin self makemockeditorwindowwithtextwidgettrue textwidget editwin text squeezer self makesqueezerinstanceeditwin squeezer autosqueezeminlines 5 squeezer countlines mockreturnvalue6 editwin write textn 6 stdout self assertequaltextwidget get 1 0 end n self assertequallensqueezer expandingbuttons 1 def testsqueezecurrenttextself squeezing text should work for both stdout and stderr prepare some text in the text widget test squeezing the current text test that expanding the squeezed text works and afterwards the text widget contains the original text test that the event doesn t squeeze text without a relevant tag editwin self makemockeditorwindowwithtextwidgettrue textwidget editwin text squeezer self makesqueezerinstanceeditwin squeezer countlines mockreturnvalue6 prepare some text in the text widget textwidget insert1 0 somentextn tag textwidget marksetinsert 1 0 self assertequaltextwidget get 1 0 end somentextnn self assertequallensqueezer expandingbuttons 0 test squeezing the current text retval squeezer squeezecurrenttext self assertequalretval break self assertequaltextwidget get 1 0 end somentextnn self assertequallensqueezer expandingbuttons 0 def testsqueezetextbeforeexistingsqueezedtextself prepare some text in the text widget and squeeze it test squeezing the current text test the reload classmethod editwin self makemockeditorwindowwithtextwidgettrue squeezer self makesqueezerinstanceeditwin origautosqueezeminlines squeezer autosqueezeminlines increase autosqueezeminlines newautosqueezeminlines origautosqueezeminlines 10 self setidleconfoptionwithcleanup main pyshell autosqueezeminlines strnewautosqueezeminlines squeezer reload self assertequalsqueezer autosqueezeminlines newautosqueezeminlines def testreloadnosqueezerinstancesself tests for the expandingbutton class in these tests the squeezer instance is a mock but actual tkinter text and button instances are created def makemocksqueezerself set default values for the configuration settings test the simplest creation of an expandingbutton squeezer self makemocksqueezer textwidget squeezer editwin text expandingbutton expandingbutton text tags 50 squeezer self assertequalexpandingbutton s text check that the underlying tkinter button is properly configured self assertequalexpandingbutton master textwidget self asserttrue 50 lines in expandingbutton cget text check that the text widget still contains no text self assertequaltextwidget get 1 0 end n check that the mouse events are bound self assertin doublebutton1 expandingbutton bind rightbuttoncode buttons 2 if macosx isaquatk else 3 self assertinrightbuttoncode expandingbutton bind check that tooltip was called once with appropriate values self assertequalmockhovertip callcount 1 mockhovertip assertcalledwithexpandingbutton any hoverdelayany check that rightclick appears in the tooltip text tooltiptext mockhovertip callargs01 self assertin rightclick tooltiptext lower def testexpandself insert the button into the text widget this is normally done by the squeezer class trigger the expand event check that the text was inserted into the text widget check that the tags tag was set on the inserted text check that the button removed itself from squeezer expandingbuttons test that expanding very long output asks user for confirmation squeezer self makemocksqueezer text a 105 expandingbutton expandingbuttontext tags 50 squeezer expandingbutton setisdangerous self asserttrueexpandingbutton isdangerous insert the button into the text widget this is normally done by the squeezer class textwidget expandingbutton text textwidget windowcreate1 0 windowexpandingbutton patch the message box module to always return false with patch idlelib squeezer messagebox as mockmsgbox mockmsgbox askokcancel returnvalue false mockmsgbox askyesno returnvalue false trigger the expand event retval expandingbutton expandeventmock check that the event chain was broken and no text was inserted self assertequalretval break self assertequalexpandingbutton text get 1 0 end1c patch the message box module to always return true with patch idlelib squeezer messagebox as mockmsgbox mockmsgbox askokcancel returnvalue true mockmsgbox askyesno returnvalue true trigger the expand event retval expandingbutton expandeventmock check that the event chain wasn t broken and the text was inserted self assertequalretval none self assertequalexpandingbutton text get 1 0 end1c text def testcopyself testing with the actual clipboard proved problematic so this test replaces the clipboard manipulation functions with mocks and checks that they are called appropriately trigger the copy event vheck that the expanding button called clipboardclear and clipboardappend text once each test the view event squeezer self makemocksqueezer expandingbutton expandingbutton text tags 50 squeezer expandingbutton selectionown mock with patch idlelib squeezer viewtext autospecviewtext as mockviewtext trigger the view event expandingbutton vieweventmock check that the expanding button called viewtext self assertequalmockviewtext callcount 1 check that the proper text was passed self assertequalmockviewtext callargs02 text def testrmenuself helper for tests create a root tk object tests for the count_lines_with_wrapping function test with an empty string test with a string which begins with a newline test with a string which ends with a newline test with several lines of text 13 characters 43 is the number of characters on this line 7 chars 13 characters tests for the squeezer class create a mock editorwindow instance create an actual squeezer instance with a mock editorwindow test squeezer count_lines with various inputs test the creation of squeezer instances test squeezer s overriding of the editorwindow s write method test squeezer s overriding of the editorwindow s write method test squeezer s overriding of the editorwindow s write method test that the auto squeezing creates an expandingbutton properly test the squeeze_current_text method squeezing text should work for both stdout and stderr prepare some text in the text widget test squeezing the current text test that expanding the squeezed text works and afterwards the text widget contains the original text test that the event doesn t squeeze text without a relevant tag prepare some text in the text widget test squeezing the current text test squeezing text before existing squeezed text prepare some text in the text widget and squeeze it test squeezing the current text test the reload class method increase auto squeeze min lines test that squeezer reload runs without any instances existing tests for the expandingbutton class in these tests the squeezer instance is a mock but actual tkinter text and button instances are created helper for tests create a mock squeezer object set default values for the configuration settings test the simplest creation of an expandingbutton check that the underlying tkinter button is properly configured check that the text widget still contains no text check that the mouse events are bound check that tooltip was called once with appropriate values check that right click appears in the tooltip text test the expand event insert the button into the text widget this is normally done by the squeezer class trigger the expand event check that the text was inserted into the text widget check that the tags tag was set on the inserted text check that the button removed itself from squeezer expandingbuttons test that expanding very long output asks user for confirmation insert the button into the text widget this is normally done by the squeezer class patch the message box module to always return false trigger the expand event check that the event chain was broken and no text was inserted patch the message box module to always return true trigger the expand event check that the event chain wasn t broken and the text was inserted test the copy event testing with the actual clipboard proved problematic so this test replaces the clipboard manipulation functions with mocks and checks that they are called appropriately trigger the copy event vheck that the expanding button called clipboard_clear and clipboard_append text once each test the view event trigger the view event check that the expanding button called view_text check that the proper text was passed test the context menu
"Test squeezer, coverage 95%"

from textwrap import dedent
from tkinter import Text, Tk
import unittest
from unittest.mock import Mock, NonCallableMagicMock, patch, sentinel, ANY
from test.support import requires

from idlelib.config import idleConf
from idlelib.percolator import Percolator
from idlelib.squeezer import count_lines_with_wrapping, ExpandingButton, \
    Squeezer
from idlelib import macosx
from idlelib.textview import view_text
from idlelib.tooltip import Hovertip

SENTINEL_VALUE = sentinel.SENTINEL_VALUE

def get_test_tk_root(test_instance):
    
    requires('gui')
    root = Tk()
    root.withdraw()

    def cleanup_root():
        root.update_idletasks()
        root.destroy()
    test_instance.addCleanup(cleanup_root)

    return root


class CountLinesTest(unittest.TestCase):
    
    def check(self, expected, text, linewidth):
        return self.assertEqual(
            expected,
            count_lines_with_wrapping(text, linewidth),
        )

    def test_count_empty(self):
        
        self.assertEqual(count_lines_with_wrapping(""), 0)

    def test_count_begins_with_empty_line(self):
        
        self.assertEqual(count_lines_with_wrapping("\ntext"), 2)

    def test_count_ends_with_empty_line(self):
        
        self.assertEqual(count_lines_with_wrapping("text\n"), 1)

    def test_count_several_lines(self):
        
        self.assertEqual(count_lines_with_wrapping("1\n2\n3\n"), 3)

    def test_empty_lines(self):
        self.check(expected=1, text='\n', linewidth=80)
        self.check(expected=2, text='\n\n', linewidth=80)
        self.check(expected=10, text='\n' * 10, linewidth=80)

    def test_long_line(self):
        self.check(expected=3, text='a' * 200, linewidth=80)
        self.check(expected=3, text='a' * 200 + '\n', linewidth=80)

    def test_several_lines_different_lengths(self):
        text = dedent()
        self.check(expected=5, text=text, linewidth=80)
        self.check(expected=5, text=text + '\n', linewidth=80)
        self.check(expected=6, text=text, linewidth=40)
        self.check(expected=7, text=text, linewidth=20)
        self.check(expected=11, text=text, linewidth=10)


class SqueezerTest(unittest.TestCase):
    
    def make_mock_editor_window(self, with_text_widget=False):
        
        editwin = NonCallableMagicMock()
        editwin.width = 80

        if with_text_widget:
            editwin.root = get_test_tk_root(self)
            text_widget = self.make_text_widget(root=editwin.root)
            editwin.text = editwin.per.bottom = text_widget

        return editwin

    def make_squeezer_instance(self, editor_window=None):
        
        if editor_window is None:
            editor_window = self.make_mock_editor_window()
        squeezer = Squeezer(editor_window)
        return squeezer

    def make_text_widget(self, root=None):
        if root is None:
            root = get_test_tk_root(self)
        text_widget = Text(root)
        text_widget["font"] = ('Courier', 10)
        text_widget.mark_set("iomark", "1.0")
        return text_widget

    def set_idleconf_option_with_cleanup(self, configType, section, option, value):
        prev_val = idleConf.GetOption(configType, section, option)
        idleConf.SetOption(configType, section, option, value)
        self.addCleanup(idleConf.SetOption,
                        configType, section, option, prev_val)

    def test_count_lines(self):
        
        editwin = self.make_mock_editor_window()
        squeezer = self.make_squeezer_instance(editwin)

        for text_code, line_width, expected in [
            (r"'\n'", 80, 1),
            (r"'\n' * 3", 80, 3),
            (r"'a' * 40 + '\n'", 80, 1),
            (r"'a' * 80 + '\n'", 80, 1),
            (r"'a' * 200 + '\n'", 80, 3),
            (r"'aa\t' * 20", 80, 2),
            (r"'aa\t' * 21", 80, 3),
            (r"'aa\t' * 20", 40, 4),
        ]:
            with self.subTest(text_code=text_code,
                              line_width=line_width,
                              expected=expected):
                text = eval(text_code)
                with patch.object(editwin, 'width', line_width):
                    self.assertEqual(squeezer.count_lines(text), expected)

    def test_init(self):
        
        editwin = self.make_mock_editor_window()
        squeezer = self.make_squeezer_instance(editwin)
        self.assertIs(squeezer.editwin, editwin)
        self.assertEqual(squeezer.expandingbuttons, [])

    def test_write_no_tags(self):
        
        editwin = self.make_mock_editor_window()
        for text in ['', 'TEXT', 'LONG TEXT' * 1000, 'MANY_LINES\n' * 100]:
            editwin.write = orig_write = Mock(return_value=SENTINEL_VALUE)
            squeezer = self.make_squeezer_instance(editwin)

            self.assertEqual(squeezer.editwin.write(text, ()), SENTINEL_VALUE)
            self.assertEqual(orig_write.call_count, 1)
            orig_write.assert_called_with(text, ())
            self.assertEqual(len(squeezer.expandingbuttons), 0)

    def test_write_not_stdout(self):
        
        for text in ['', 'TEXT', 'LONG TEXT' * 1000, 'MANY_LINES\n' * 100]:
            editwin = self.make_mock_editor_window()
            editwin.write.return_value = SENTINEL_VALUE
            orig_write = editwin.write
            squeezer = self.make_squeezer_instance(editwin)

            self.assertEqual(squeezer.editwin.write(text, "stderr"),
                              SENTINEL_VALUE)
            self.assertEqual(orig_write.call_count, 1)
            orig_write.assert_called_with(text, "stderr")
            self.assertEqual(len(squeezer.expandingbuttons), 0)

    def test_write_stdout(self):
        
        editwin = self.make_mock_editor_window()

        for text in ['', 'TEXT']:
            editwin.write = orig_write = Mock(return_value=SENTINEL_VALUE)
            squeezer = self.make_squeezer_instance(editwin)
            squeezer.auto_squeeze_min_lines = 50

            self.assertEqual(squeezer.editwin.write(text, "stdout"),
                             SENTINEL_VALUE)
            self.assertEqual(orig_write.call_count, 1)
            orig_write.assert_called_with(text, "stdout")
            self.assertEqual(len(squeezer.expandingbuttons), 0)

        for text in ['LONG TEXT' * 1000, 'MANY_LINES\n' * 100]:
            editwin.write = orig_write = Mock(return_value=SENTINEL_VALUE)
            squeezer = self.make_squeezer_instance(editwin)
            squeezer.auto_squeeze_min_lines = 50

            self.assertEqual(squeezer.editwin.write(text, "stdout"), None)
            self.assertEqual(orig_write.call_count, 0)
            self.assertEqual(len(squeezer.expandingbuttons), 1)

    def test_auto_squeeze(self):
        
        editwin = self.make_mock_editor_window(with_text_widget=True)
        text_widget = editwin.text
        squeezer = self.make_squeezer_instance(editwin)
        squeezer.auto_squeeze_min_lines = 5
        squeezer.count_lines = Mock(return_value=6)

        editwin.write('TEXT\n'*6, "stdout")
        self.assertEqual(text_widget.get('1.0', 'end'), '\n')
        self.assertEqual(len(squeezer.expandingbuttons), 1)

    def test_squeeze_current_text(self):
           
        for tag_name in ["stdout", "stderr"]:
            editwin = self.make_mock_editor_window(with_text_widget=True)
            text_widget = editwin.text
            squeezer = self.make_squeezer_instance(editwin)
            squeezer.count_lines = Mock(return_value=6)

            
            text_widget.insert("1.0", "SOME\nTEXT\n", tag_name)
            text_widget.mark_set("insert", "1.0")
            self.assertEqual(text_widget.get('1.0', 'end'), 'SOME\nTEXT\n\n')

            self.assertEqual(len(squeezer.expandingbuttons), 0)

            
            retval = squeezer.squeeze_current_text()
            self.assertEqual(retval, "break")
            self.assertEqual(text_widget.get('1.0', 'end'), '\n\n')
            self.assertEqual(len(squeezer.expandingbuttons), 1)
            self.assertEqual(squeezer.expandingbuttons[0].s, 'SOME\nTEXT')

            squeezer.expandingbuttons[0].expand()
            self.assertEqual(text_widget.get('1.0', 'end'), 'SOME\nTEXT\n\n')
            self.assertEqual(len(squeezer.expandingbuttons), 0)

    def test_squeeze_current_text_no_allowed_tags(self):
        
        editwin = self.make_mock_editor_window(with_text_widget=True)
        text_widget = editwin.text
        squeezer = self.make_squeezer_instance(editwin)
        squeezer.count_lines = Mock(return_value=6)

        
        text_widget.insert("1.0", "SOME\nTEXT\n", "TAG")
        text_widget.mark_set("insert", "1.0")
        self.assertEqual(text_widget.get('1.0', 'end'), 'SOME\nTEXT\n\n')

        self.assertEqual(len(squeezer.expandingbuttons), 0)

        
        retval = squeezer.squeeze_current_text()
        self.assertEqual(retval, "break")
        self.assertEqual(text_widget.get('1.0', 'end'), 'SOME\nTEXT\n\n')
        self.assertEqual(len(squeezer.expandingbuttons), 0)

    def test_squeeze_text_before_existing_squeezed_text(self):
        
        editwin = self.make_mock_editor_window(with_text_widget=True)
        text_widget = editwin.text
        squeezer = self.make_squeezer_instance(editwin)
        squeezer.count_lines = Mock(return_value=6)

        
        text_widget.insert("1.0", "SOME\nTEXT\n", "stdout")
        text_widget.mark_set("insert", "1.0")
        squeezer.squeeze_current_text()
        self.assertEqual(len(squeezer.expandingbuttons), 1)

        
        text_widget.insert("1.0", "MORE\nSTUFF\n", "stdout")
        text_widget.mark_set("insert", "1.0")
        retval = squeezer.squeeze_current_text()
        self.assertEqual(retval, "break")
        self.assertEqual(text_widget.get('1.0', 'end'), '\n\n\n')
        self.assertEqual(len(squeezer.expandingbuttons), 2)
        self.assertTrue(text_widget.compare(
            squeezer.expandingbuttons[0],
            '<',
            squeezer.expandingbuttons[1],
        ))

    def test_reload(self):
        
        editwin = self.make_mock_editor_window(with_text_widget=True)
        squeezer = self.make_squeezer_instance(editwin)

        orig_auto_squeeze_min_lines = squeezer.auto_squeeze_min_lines

        
        new_auto_squeeze_min_lines = orig_auto_squeeze_min_lines + 10
        self.set_idleconf_option_with_cleanup(
            'main', 'PyShell', 'auto-squeeze-min-lines',
            str(new_auto_squeeze_min_lines))

        Squeezer.reload()
        self.assertEqual(squeezer.auto_squeeze_min_lines,
                         new_auto_squeeze_min_lines)

    def test_reload_no_squeezer_instances(self):
        
        Squeezer.reload()


class ExpandingButtonTest(unittest.TestCase):
    
    
    
    def make_mock_squeezer(self):
        
        root = get_test_tk_root(self)
        squeezer = Mock()
        squeezer.editwin.text = Text(root)
        squeezer.editwin.per = Percolator(squeezer.editwin.text)
        self.addCleanup(squeezer.editwin.per.close)

        
        squeezer.auto_squeeze_min_lines = 50
        return squeezer

    @patch('idlelib.squeezer.Hovertip', autospec=Hovertip)
    def test_init(self, MockHovertip):
        
        squeezer = self.make_mock_squeezer()
        text_widget = squeezer.editwin.text

        expandingbutton = ExpandingButton('TEXT', 'TAGS', 50, squeezer)
        self.assertEqual(expandingbutton.s, 'TEXT')

        
        self.assertEqual(expandingbutton.master, text_widget)
        self.assertTrue('50 lines' in expandingbutton.cget('text'))

        
        self.assertEqual(text_widget.get('1.0', 'end'), '\n')

        
        self.assertIn('<Double-Button-1>', expandingbutton.bind())
        right_button_code = '<Button-%s>' % ('2' if macosx.isAquaTk() else '3')
        self.assertIn(right_button_code, expandingbutton.bind())

        
        self.assertEqual(MockHovertip.call_count, 1)
        MockHovertip.assert_called_with(expandingbutton, ANY, hover_delay=ANY)

        
        tooltip_text = MockHovertip.call_args[0][1]
        self.assertIn('right-click', tooltip_text.lower())

    def test_expand(self):
        
        squeezer = self.make_mock_squeezer()
        expandingbutton = ExpandingButton('TEXT', 'TAGS', 50, squeezer)

        
        
        text_widget = squeezer.editwin.text
        text_widget.window_create("1.0", window=expandingbutton)

        
        retval = expandingbutton.expand(event=Mock())
        self.assertEqual(retval, None)

        
        self.assertEqual(text_widget.get('1.0', 'end'), 'TEXT\n')

        
        text_end_index = text_widget.index('end-1c')
        self.assertEqual(text_widget.get('1.0', text_end_index), 'TEXT')
        self.assertEqual(text_widget.tag_nextrange('TAGS', '1.0'),
                          ('1.0', text_end_index))

        
        self.assertEqual(squeezer.expandingbuttons.remove.call_count, 1)
        squeezer.expandingbuttons.remove.assert_called_with(expandingbutton)

    def test_expand_dangerous_oupput(self):
        
        squeezer = self.make_mock_squeezer()
        text = 'a' * 10**5
        expandingbutton = ExpandingButton(text, 'TAGS', 50, squeezer)
        expandingbutton.set_is_dangerous()
        self.assertTrue(expandingbutton.is_dangerous)

        
        
        text_widget = expandingbutton.text
        text_widget.window_create("1.0", window=expandingbutton)

        
        with patch('idlelib.squeezer.messagebox') as mock_msgbox:
            mock_msgbox.askokcancel.return_value = False
            mock_msgbox.askyesno.return_value = False
            
            retval = expandingbutton.expand(event=Mock())

        
        self.assertEqual(retval, 'break')
        self.assertEqual(expandingbutton.text.get('1.0', 'end-1c'), '')

        
        with patch('idlelib.squeezer.messagebox') as mock_msgbox:
            mock_msgbox.askokcancel.return_value = True
            mock_msgbox.askyesno.return_value = True
            
            retval = expandingbutton.expand(event=Mock())

        
        self.assertEqual(retval, None)
        self.assertEqual(expandingbutton.text.get('1.0', 'end-1c'), text)

    def test_copy(self):
        
        
        
        
        squeezer = self.make_mock_squeezer()
        expandingbutton = ExpandingButton('TEXT', 'TAGS', 50, squeezer)
        expandingbutton.clipboard_clear = Mock()
        expandingbutton.clipboard_append = Mock()

        
        retval = expandingbutton.copy(event=Mock())
        self.assertEqual(retval, None)

        
        
        self.assertEqual(expandingbutton.clipboard_clear.call_count, 1)
        self.assertEqual(expandingbutton.clipboard_append.call_count, 1)
        expandingbutton.clipboard_append.assert_called_with('TEXT')

    def test_view(self):
        
        squeezer = self.make_mock_squeezer()
        expandingbutton = ExpandingButton('TEXT', 'TAGS', 50, squeezer)
        expandingbutton.selection_own = Mock()

        with patch('idlelib.squeezer.view_text', autospec=view_text)\
                as mock_view_text:
            
            expandingbutton.view(event=Mock())

            
            self.assertEqual(mock_view_text.call_count, 1)

            
            self.assertEqual(mock_view_text.call_args[0][2], 'TEXT')

    def test_rmenu(self):
        
        squeezer = self.make_mock_squeezer()
        expandingbutton = ExpandingButton('TEXT', 'TAGS', 50, squeezer)
        with patch('tkinter.Menu') as mock_Menu:
            mock_menu = Mock()
            mock_Menu.return_value = mock_menu
            mock_event = Mock()
            mock_event.x = 10
            mock_event.y = 10
            expandingbutton.context_menu_event(event=mock_event)
            self.assertEqual(mock_menu.add_command.call_count,
                             len(expandingbutton.rmenu_specs))
            for label, *data in expandingbutton.rmenu_specs:
                mock_menu.add_command.assert_any_call(label=label, command=ANY)


if __name__ == '__main__':
    unittest.main(verbosity=2)