Coverage for idle_test/test_squeezer.py: 49%
277 statements
« prev ^ index » next coverage.py v7.2.5, created at 2023-05-11 13:22 -0700
« prev ^ index » next coverage.py v7.2.5, created at 2023-05-11 13:22 -0700
1"Test squeezer, coverage 95%"
3from textwrap import dedent
4from tkinter import Text, Tk
5import unittest
6from unittest.mock import Mock, NonCallableMagicMock, patch, sentinel, ANY
7from test.support import requires
9from idlelib.config import idleConf
10from idlelib.percolator import Percolator
11from idlelib.squeezer import count_lines_with_wrapping, ExpandingButton, \
12 Squeezer
13from idlelib import macosx
14from idlelib.textview import view_text
15from idlelib.tooltip import Hovertip
17SENTINEL_VALUE = sentinel.SENTINEL_VALUE
20def get_test_tk_root(test_instance):
21 """Helper for tests: Create a root Tk object."""
22 requires('gui') 1nopqrsijgkl
23 root = Tk()
24 root.withdraw()
26 def cleanup_root():
27 root.update_idletasks()
28 root.destroy()
29 test_instance.addCleanup(cleanup_root)
31 return root
34class CountLinesTest(unittest.TestCase):
35 """Tests for the count_lines_with_wrapping function."""
36 def check(self, expected, text, linewidth):
37 return self.assertEqual( 1mth
38 expected,
39 count_lines_with_wrapping(text, linewidth),
40 )
42 def test_count_empty(self):
43 """Test with an empty string."""
44 self.assertEqual(count_lines_with_wrapping(""), 0) 1u
46 def test_count_begins_with_empty_line(self):
47 """Test with a string which begins with a newline."""
48 self.assertEqual(count_lines_with_wrapping("\ntext"), 2) 1v
50 def test_count_ends_with_empty_line(self):
51 """Test with a string which ends with a newline."""
52 self.assertEqual(count_lines_with_wrapping("text\n"), 1) 1w
54 def test_count_several_lines(self):
55 """Test with several lines of text."""
56 self.assertEqual(count_lines_with_wrapping("1\n2\n3\n"), 3) 1x
58 def test_empty_lines(self):
59 self.check(expected=1, text='\n', linewidth=80) 1m
60 self.check(expected=2, text='\n\n', linewidth=80) 1m
61 self.check(expected=10, text='\n' * 10, linewidth=80) 1m
63 def test_long_line(self):
64 self.check(expected=3, text='a' * 200, linewidth=80) 1t
65 self.check(expected=3, text='a' * 200 + '\n', linewidth=80) 1t
67 def test_several_lines_different_lengths(self):
68 text = dedent("""\ 1h
69 13 characters
70 43 is the number of characters on this line
72 7 chars
73 13 characters""")
74 self.check(expected=5, text=text, linewidth=80) 1h
75 self.check(expected=5, text=text + '\n', linewidth=80) 1h
76 self.check(expected=6, text=text, linewidth=40) 1h
77 self.check(expected=7, text=text, linewidth=20) 1h
78 self.check(expected=11, text=text, linewidth=10) 1h
81class SqueezerTest(unittest.TestCase):
82 """Tests for the Squeezer class."""
83 def make_mock_editor_window(self, with_text_widget=False):
84 """Create a mock EditorWindow instance."""
85 editwin = NonCallableMagicMock() 1iefjgkldcb
86 editwin.width = 80 1iefjgkldcb
88 if with_text_widget: 1iefjgkldcb
89 editwin.root = get_test_tk_root(self) 1ijgkl
90 text_widget = self.make_text_widget(root=editwin.root)
91 editwin.text = editwin.per.bottom = text_widget
93 return editwin 1efdcb
95 def make_squeezer_instance(self, editor_window=None):
96 """Create an actual Squeezer instance with a mock EditorWindow."""
97 if editor_window is None: 97 ↛ 98line 97 didn't jump to line 98, because the condition on line 97 was never true1efdcb
98 editor_window = self.make_mock_editor_window()
99 squeezer = Squeezer(editor_window) 1efdcb
100 return squeezer 1efdcb
102 def make_text_widget(self, root=None):
103 if root is None:
104 root = get_test_tk_root(self)
105 text_widget = Text(root)
106 text_widget["font"] = ('Courier', 10)
107 text_widget.mark_set("iomark", "1.0")
108 return text_widget
110 def set_idleconf_option_with_cleanup(self, configType, section, option, value):
111 prev_val = idleConf.GetOption(configType, section, option)
112 idleConf.SetOption(configType, section, option, value)
113 self.addCleanup(idleConf.SetOption,
114 configType, section, option, prev_val)
116 def test_count_lines(self):
117 """Test Squeezer.count_lines() with various inputs."""
118 editwin = self.make_mock_editor_window() 1e
119 squeezer = self.make_squeezer_instance(editwin) 1e
121 for text_code, line_width, expected in [ 1e
122 (r"'\n'", 80, 1),
123 (r"'\n' * 3", 80, 3),
124 (r"'a' * 40 + '\n'", 80, 1),
125 (r"'a' * 80 + '\n'", 80, 1),
126 (r"'a' * 200 + '\n'", 80, 3),
127 (r"'aa\t' * 20", 80, 2),
128 (r"'aa\t' * 21", 80, 3),
129 (r"'aa\t' * 20", 40, 4),
130 ]:
131 with self.subTest(text_code=text_code, 1e
132 line_width=line_width,
133 expected=expected):
134 text = eval(text_code) 1e
135 with patch.object(editwin, 'width', line_width): 1e
136 self.assertEqual(squeezer.count_lines(text), expected) 1e
138 def test_init(self):
139 """Test the creation of Squeezer instances."""
140 editwin = self.make_mock_editor_window() 1f
141 squeezer = self.make_squeezer_instance(editwin) 1f
142 self.assertIs(squeezer.editwin, editwin) 1f
143 self.assertEqual(squeezer.expandingbuttons, []) 1f
145 def test_write_no_tags(self):
146 """Test Squeezer's overriding of the EditorWindow's write() method."""
147 editwin = self.make_mock_editor_window() 1d
148 for text in ['', 'TEXT', 'LONG TEXT' * 1000, 'MANY_LINES\n' * 100]: 1d
149 editwin.write = orig_write = Mock(return_value=SENTINEL_VALUE) 1d
150 squeezer = self.make_squeezer_instance(editwin) 1d
152 self.assertEqual(squeezer.editwin.write(text, ()), SENTINEL_VALUE) 1d
153 self.assertEqual(orig_write.call_count, 1) 1d
154 orig_write.assert_called_with(text, ()) 1d
155 self.assertEqual(len(squeezer.expandingbuttons), 0) 1d
157 def test_write_not_stdout(self):
158 """Test Squeezer's overriding of the EditorWindow's write() method."""
159 for text in ['', 'TEXT', 'LONG TEXT' * 1000, 'MANY_LINES\n' * 100]: 1c
160 editwin = self.make_mock_editor_window() 1c
161 editwin.write.return_value = SENTINEL_VALUE 1c
162 orig_write = editwin.write 1c
163 squeezer = self.make_squeezer_instance(editwin) 1c
165 self.assertEqual(squeezer.editwin.write(text, "stderr"), 1c
166 SENTINEL_VALUE)
167 self.assertEqual(orig_write.call_count, 1) 1c
168 orig_write.assert_called_with(text, "stderr") 1c
169 self.assertEqual(len(squeezer.expandingbuttons), 0) 1c
171 def test_write_stdout(self):
172 """Test Squeezer's overriding of the EditorWindow's write() method."""
173 editwin = self.make_mock_editor_window() 1b
175 for text in ['', 'TEXT']: 1b
176 editwin.write = orig_write = Mock(return_value=SENTINEL_VALUE) 1b
177 squeezer = self.make_squeezer_instance(editwin) 1b
178 squeezer.auto_squeeze_min_lines = 50 1b
180 self.assertEqual(squeezer.editwin.write(text, "stdout"), 1b
181 SENTINEL_VALUE)
182 self.assertEqual(orig_write.call_count, 1) 1b
183 orig_write.assert_called_with(text, "stdout") 1b
184 self.assertEqual(len(squeezer.expandingbuttons), 0) 1b
186 for text in ['LONG TEXT' * 1000, 'MANY_LINES\n' * 100]: 1b
187 editwin.write = orig_write = Mock(return_value=SENTINEL_VALUE) 1b
188 squeezer = self.make_squeezer_instance(editwin) 1b
189 squeezer.auto_squeeze_min_lines = 50 1b
191 self.assertEqual(squeezer.editwin.write(text, "stdout"), None) 1b
192 self.assertEqual(orig_write.call_count, 0) 1b
193 self.assertEqual(len(squeezer.expandingbuttons), 1) 1b
195 def test_auto_squeeze(self):
196 """Test that the auto-squeezing creates an ExpandingButton properly."""
197 editwin = self.make_mock_editor_window(with_text_widget=True) 1i
198 text_widget = editwin.text
199 squeezer = self.make_squeezer_instance(editwin)
200 squeezer.auto_squeeze_min_lines = 5
201 squeezer.count_lines = Mock(return_value=6)
203 editwin.write('TEXT\n'*6, "stdout")
204 self.assertEqual(text_widget.get('1.0', 'end'), '\n')
205 self.assertEqual(len(squeezer.expandingbuttons), 1)
207 def test_squeeze_current_text(self):
208 """Test the squeeze_current_text method."""
209 # Squeezing text should work for both stdout and stderr.
210 for tag_name in ["stdout", "stderr"]: 210 ↛ exitline 210 didn't return from function 'test_squeeze_current_text', because the loop on line 210 didn't complete1g
211 editwin = self.make_mock_editor_window(with_text_widget=True) 1g
212 text_widget = editwin.text
213 squeezer = self.make_squeezer_instance(editwin)
214 squeezer.count_lines = Mock(return_value=6)
216 # Prepare some text in the Text widget.
217 text_widget.insert("1.0", "SOME\nTEXT\n", tag_name)
218 text_widget.mark_set("insert", "1.0")
219 self.assertEqual(text_widget.get('1.0', 'end'), 'SOME\nTEXT\n\n')
221 self.assertEqual(len(squeezer.expandingbuttons), 0)
223 # Test squeezing the current text.
224 retval = squeezer.squeeze_current_text()
225 self.assertEqual(retval, "break")
226 self.assertEqual(text_widget.get('1.0', 'end'), '\n\n')
227 self.assertEqual(len(squeezer.expandingbuttons), 1)
228 self.assertEqual(squeezer.expandingbuttons[0].s, 'SOME\nTEXT')
230 # Test that expanding the squeezed text works and afterwards
231 # the Text widget contains the original text.
232 squeezer.expandingbuttons[0].expand()
233 self.assertEqual(text_widget.get('1.0', 'end'), 'SOME\nTEXT\n\n')
234 self.assertEqual(len(squeezer.expandingbuttons), 0)
236 def test_squeeze_current_text_no_allowed_tags(self):
237 """Test that the event doesn't squeeze text without a relevant tag."""
238 editwin = self.make_mock_editor_window(with_text_widget=True) 1k
239 text_widget = editwin.text
240 squeezer = self.make_squeezer_instance(editwin)
241 squeezer.count_lines = Mock(return_value=6)
243 # Prepare some text in the Text widget.
244 text_widget.insert("1.0", "SOME\nTEXT\n", "TAG")
245 text_widget.mark_set("insert", "1.0")
246 self.assertEqual(text_widget.get('1.0', 'end'), 'SOME\nTEXT\n\n')
248 self.assertEqual(len(squeezer.expandingbuttons), 0)
250 # Test squeezing the current text.
251 retval = squeezer.squeeze_current_text()
252 self.assertEqual(retval, "break")
253 self.assertEqual(text_widget.get('1.0', 'end'), 'SOME\nTEXT\n\n')
254 self.assertEqual(len(squeezer.expandingbuttons), 0)
256 def test_squeeze_text_before_existing_squeezed_text(self):
257 """Test squeezing text before existing squeezed text."""
258 editwin = self.make_mock_editor_window(with_text_widget=True) 1l
259 text_widget = editwin.text
260 squeezer = self.make_squeezer_instance(editwin)
261 squeezer.count_lines = Mock(return_value=6)
263 # Prepare some text in the Text widget and squeeze it.
264 text_widget.insert("1.0", "SOME\nTEXT\n", "stdout")
265 text_widget.mark_set("insert", "1.0")
266 squeezer.squeeze_current_text()
267 self.assertEqual(len(squeezer.expandingbuttons), 1)
269 # Test squeezing the current text.
270 text_widget.insert("1.0", "MORE\nSTUFF\n", "stdout")
271 text_widget.mark_set("insert", "1.0")
272 retval = squeezer.squeeze_current_text()
273 self.assertEqual(retval, "break")
274 self.assertEqual(text_widget.get('1.0', 'end'), '\n\n\n')
275 self.assertEqual(len(squeezer.expandingbuttons), 2)
276 self.assertTrue(text_widget.compare(
277 squeezer.expandingbuttons[0],
278 '<',
279 squeezer.expandingbuttons[1],
280 ))
282 def test_reload(self):
283 """Test the reload() class-method."""
284 editwin = self.make_mock_editor_window(with_text_widget=True) 1j
285 squeezer = self.make_squeezer_instance(editwin)
287 orig_auto_squeeze_min_lines = squeezer.auto_squeeze_min_lines
289 # Increase auto-squeeze-min-lines.
290 new_auto_squeeze_min_lines = orig_auto_squeeze_min_lines + 10
291 self.set_idleconf_option_with_cleanup(
292 'main', 'PyShell', 'auto-squeeze-min-lines',
293 str(new_auto_squeeze_min_lines))
295 Squeezer.reload()
296 self.assertEqual(squeezer.auto_squeeze_min_lines,
297 new_auto_squeeze_min_lines)
299 def test_reload_no_squeezer_instances(self):
300 """Test that Squeezer.reload() runs without any instances existing."""
301 Squeezer.reload() 1y
304class ExpandingButtonTest(unittest.TestCase):
305 """Tests for the ExpandingButton class."""
306 # In these tests the squeezer instance is a mock, but actual tkinter
307 # Text and Button instances are created.
308 def make_mock_squeezer(self):
309 """Helper for tests: Create a mock Squeezer object."""
310 root = get_test_tk_root(self) 1nopqrs
311 squeezer = Mock()
312 squeezer.editwin.text = Text(root)
313 squeezer.editwin.per = Percolator(squeezer.editwin.text)
314 self.addCleanup(squeezer.editwin.per.close)
316 # Set default values for the configuration settings.
317 squeezer.auto_squeeze_min_lines = 50
318 return squeezer
320 @patch('idlelib.squeezer.Hovertip', autospec=Hovertip)
321 def test_init(self, MockHovertip):
322 """Test the simplest creation of an ExpandingButton."""
323 squeezer = self.make_mock_squeezer() 1q
324 text_widget = squeezer.editwin.text
326 expandingbutton = ExpandingButton('TEXT', 'TAGS', 50, squeezer)
327 self.assertEqual(expandingbutton.s, 'TEXT')
329 # Check that the underlying tkinter.Button is properly configured.
330 self.assertEqual(expandingbutton.master, text_widget)
331 self.assertTrue('50 lines' in expandingbutton.cget('text'))
333 # Check that the text widget still contains no text.
334 self.assertEqual(text_widget.get('1.0', 'end'), '\n')
336 # Check that the mouse events are bound.
337 self.assertIn('<Double-Button-1>', expandingbutton.bind())
338 right_button_code = '<Button-%s>' % ('2' if macosx.isAquaTk() else '3')
339 self.assertIn(right_button_code, expandingbutton.bind())
341 # Check that ToolTip was called once, with appropriate values.
342 self.assertEqual(MockHovertip.call_count, 1)
343 MockHovertip.assert_called_with(expandingbutton, ANY, hover_delay=ANY)
345 # Check that 'right-click' appears in the tooltip text.
346 tooltip_text = MockHovertip.call_args[0][1]
347 self.assertIn('right-click', tooltip_text.lower())
349 def test_expand(self):
350 """Test the expand event."""
351 squeezer = self.make_mock_squeezer() 1o
352 expandingbutton = ExpandingButton('TEXT', 'TAGS', 50, squeezer)
354 # Insert the button into the text widget
355 # (this is normally done by the Squeezer class).
356 text_widget = squeezer.editwin.text
357 text_widget.window_create("1.0", window=expandingbutton)
359 # trigger the expand event
360 retval = expandingbutton.expand(event=Mock())
361 self.assertEqual(retval, None)
363 # Check that the text was inserted into the text widget.
364 self.assertEqual(text_widget.get('1.0', 'end'), 'TEXT\n')
366 # Check that the 'TAGS' tag was set on the inserted text.
367 text_end_index = text_widget.index('end-1c')
368 self.assertEqual(text_widget.get('1.0', text_end_index), 'TEXT')
369 self.assertEqual(text_widget.tag_nextrange('TAGS', '1.0'),
370 ('1.0', text_end_index))
372 # Check that the button removed itself from squeezer.expandingbuttons.
373 self.assertEqual(squeezer.expandingbuttons.remove.call_count, 1)
374 squeezer.expandingbuttons.remove.assert_called_with(expandingbutton)
376 def test_expand_dangerous_oupput(self):
377 """Test that expanding very long output asks user for confirmation."""
378 squeezer = self.make_mock_squeezer() 1p
379 text = 'a' * 10**5
380 expandingbutton = ExpandingButton(text, 'TAGS', 50, squeezer)
381 expandingbutton.set_is_dangerous()
382 self.assertTrue(expandingbutton.is_dangerous)
384 # Insert the button into the text widget
385 # (this is normally done by the Squeezer class).
386 text_widget = expandingbutton.text
387 text_widget.window_create("1.0", window=expandingbutton)
389 # Patch the message box module to always return False.
390 with patch('idlelib.squeezer.messagebox') as mock_msgbox:
391 mock_msgbox.askokcancel.return_value = False
392 mock_msgbox.askyesno.return_value = False
393 # Trigger the expand event.
394 retval = expandingbutton.expand(event=Mock())
396 # Check that the event chain was broken and no text was inserted.
397 self.assertEqual(retval, 'break')
398 self.assertEqual(expandingbutton.text.get('1.0', 'end-1c'), '')
400 # Patch the message box module to always return True.
401 with patch('idlelib.squeezer.messagebox') as mock_msgbox:
402 mock_msgbox.askokcancel.return_value = True
403 mock_msgbox.askyesno.return_value = True
404 # Trigger the expand event.
405 retval = expandingbutton.expand(event=Mock())
407 # Check that the event chain wasn't broken and the text was inserted.
408 self.assertEqual(retval, None)
409 self.assertEqual(expandingbutton.text.get('1.0', 'end-1c'), text)
411 def test_copy(self):
412 """Test the copy event."""
413 # Testing with the actual clipboard proved problematic, so this
414 # test replaces the clipboard manipulation functions with mocks
415 # and checks that they are called appropriately.
416 squeezer = self.make_mock_squeezer() 1n
417 expandingbutton = ExpandingButton('TEXT', 'TAGS', 50, squeezer)
418 expandingbutton.clipboard_clear = Mock()
419 expandingbutton.clipboard_append = Mock()
421 # Trigger the copy event.
422 retval = expandingbutton.copy(event=Mock())
423 self.assertEqual(retval, None)
425 # Vheck that the expanding button called clipboard_clear() and
426 # clipboard_append('TEXT') once each.
427 self.assertEqual(expandingbutton.clipboard_clear.call_count, 1)
428 self.assertEqual(expandingbutton.clipboard_append.call_count, 1)
429 expandingbutton.clipboard_append.assert_called_with('TEXT')
431 def test_view(self):
432 """Test the view event."""
433 squeezer = self.make_mock_squeezer() 1s
434 expandingbutton = ExpandingButton('TEXT', 'TAGS', 50, squeezer)
435 expandingbutton.selection_own = Mock()
437 with patch('idlelib.squeezer.view_text', autospec=view_text)\
438 as mock_view_text:
439 # Trigger the view event.
440 expandingbutton.view(event=Mock())
442 # Check that the expanding button called view_text.
443 self.assertEqual(mock_view_text.call_count, 1)
445 # Check that the proper text was passed.
446 self.assertEqual(mock_view_text.call_args[0][2], 'TEXT')
448 def test_rmenu(self):
449 """Test the context menu."""
450 squeezer = self.make_mock_squeezer() 1r
451 expandingbutton = ExpandingButton('TEXT', 'TAGS', 50, squeezer)
452 with patch('tkinter.Menu') as mock_Menu:
453 mock_menu = Mock()
454 mock_Menu.return_value = mock_menu
455 mock_event = Mock()
456 mock_event.x = 10
457 mock_event.y = 10
458 expandingbutton.context_menu_event(event=mock_event)
459 self.assertEqual(mock_menu.add_command.call_count,
460 len(expandingbutton.rmenu_specs))
461 for label, *data in expandingbutton.rmenu_specs:
462 mock_menu.add_command.assert_any_call(label=label, command=ANY)
465if __name__ == '__main__': 465 ↛ 466line 465 didn't jump to line 466, because the condition on line 465 was never true
466 unittest.main(verbosity=2)