509 lines
22 KiB
Python
509 lines
22 KiB
Python
from collections import namedtuple
|
|
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.squeezer import count_lines_with_wrapping, ExpandingButton, \
|
|
Squeezer
|
|
from idlelib import macosx
|
|
from idlelib.textview import view_text
|
|
from idlelib.tooltip import Hovertip
|
|
from idlelib.pyshell import PyShell
|
|
|
|
|
|
SENTINEL_VALUE = sentinel.SENTINEL_VALUE
|
|
|
|
|
|
def get_test_tk_root(test_instance):
|
|
"""Helper for tests: Create a root Tk object."""
|
|
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):
|
|
"""Tests for the count_lines_with_wrapping function."""
|
|
def check(self, expected, text, linewidth, tabwidth):
|
|
return self.assertEqual(
|
|
expected,
|
|
count_lines_with_wrapping(text, linewidth, tabwidth),
|
|
)
|
|
|
|
def test_count_empty(self):
|
|
"""Test with an empty string."""
|
|
self.assertEqual(count_lines_with_wrapping(""), 0)
|
|
|
|
def test_count_begins_with_empty_line(self):
|
|
"""Test with a string which begins with a newline."""
|
|
self.assertEqual(count_lines_with_wrapping("\ntext"), 2)
|
|
|
|
def test_count_ends_with_empty_line(self):
|
|
"""Test with a string which ends with a newline."""
|
|
self.assertEqual(count_lines_with_wrapping("text\n"), 1)
|
|
|
|
def test_count_several_lines(self):
|
|
"""Test with several lines of text."""
|
|
self.assertEqual(count_lines_with_wrapping("1\n2\n3\n"), 3)
|
|
|
|
def test_tab_width(self):
|
|
"""Test with various tab widths and line widths."""
|
|
self.check(expected=1, text='\t' * 1, linewidth=8, tabwidth=4)
|
|
self.check(expected=1, text='\t' * 2, linewidth=8, tabwidth=4)
|
|
self.check(expected=2, text='\t' * 3, linewidth=8, tabwidth=4)
|
|
self.check(expected=2, text='\t' * 4, linewidth=8, tabwidth=4)
|
|
self.check(expected=3, text='\t' * 5, linewidth=8, tabwidth=4)
|
|
|
|
# test longer lines and various tab widths
|
|
self.check(expected=4, text='\t' * 10, linewidth=12, tabwidth=4)
|
|
self.check(expected=10, text='\t' * 10, linewidth=12, tabwidth=8)
|
|
self.check(expected=2, text='\t' * 4, linewidth=10, tabwidth=3)
|
|
|
|
# test tabwidth=1
|
|
self.check(expected=2, text='\t' * 9, linewidth=5, tabwidth=1)
|
|
self.check(expected=2, text='\t' * 10, linewidth=5, tabwidth=1)
|
|
self.check(expected=3, text='\t' * 11, linewidth=5, tabwidth=1)
|
|
|
|
# test for off-by-one errors
|
|
self.check(expected=2, text='\t' * 6, linewidth=12, tabwidth=4)
|
|
self.check(expected=3, text='\t' * 6, linewidth=11, tabwidth=4)
|
|
self.check(expected=2, text='\t' * 6, linewidth=13, tabwidth=4)
|
|
|
|
|
|
class SqueezerTest(unittest.TestCase):
|
|
"""Tests for the Squeezer class."""
|
|
def make_mock_editor_window(self):
|
|
"""Create a mock EditorWindow instance."""
|
|
editwin = NonCallableMagicMock()
|
|
# isinstance(editwin, PyShell) must be true for Squeezer to enable
|
|
# auto-squeezing; in practice this will always be true
|
|
editwin.__class__ = PyShell
|
|
return editwin
|
|
|
|
def make_squeezer_instance(self, editor_window=None):
|
|
"""Create an actual Squeezer instance with a mock EditorWindow."""
|
|
if editor_window is None:
|
|
editor_window = self.make_mock_editor_window()
|
|
return Squeezer(editor_window)
|
|
|
|
def test_count_lines(self):
|
|
"""Test Squeezer.count_lines() with various inputs.
|
|
|
|
This checks that Squeezer.count_lines() calls the
|
|
count_lines_with_wrapping() function with the appropriate parameters.
|
|
"""
|
|
for tabwidth, linewidth in [(4, 80), (1, 79), (8, 80), (3, 120)]:
|
|
self._test_count_lines_helper(linewidth=linewidth,
|
|
tabwidth=tabwidth)
|
|
|
|
def _prepare_mock_editwin_for_count_lines(self, editwin,
|
|
linewidth, tabwidth):
|
|
"""Prepare a mock EditorWindow object for Squeezer.count_lines."""
|
|
CHAR_WIDTH = 10
|
|
BORDER_WIDTH = 2
|
|
PADDING_WIDTH = 1
|
|
|
|
# Prepare all the required functionality on the mock EditorWindow object
|
|
# so that the calculations in Squeezer.count_lines() can run.
|
|
editwin.get_tk_tabwidth.return_value = tabwidth
|
|
editwin.text.winfo_width.return_value = \
|
|
linewidth * CHAR_WIDTH + 2 * (BORDER_WIDTH + PADDING_WIDTH)
|
|
text_opts = {
|
|
'border': BORDER_WIDTH,
|
|
'padx': PADDING_WIDTH,
|
|
'font': None,
|
|
}
|
|
editwin.text.cget = lambda opt: text_opts[opt]
|
|
|
|
# monkey-path tkinter.font.Font with a mock object, so that
|
|
# Font.measure('0') returns CHAR_WIDTH
|
|
mock_font = Mock()
|
|
def measure(char):
|
|
if char == '0':
|
|
return CHAR_WIDTH
|
|
raise ValueError("measure should only be called on '0'!")
|
|
mock_font.return_value.measure = measure
|
|
patcher = patch('idlelib.squeezer.Font', mock_font)
|
|
patcher.start()
|
|
self.addCleanup(patcher.stop)
|
|
|
|
def _test_count_lines_helper(self, linewidth, tabwidth):
|
|
"""Helper for test_count_lines."""
|
|
editwin = self.make_mock_editor_window()
|
|
self._prepare_mock_editwin_for_count_lines(editwin, linewidth, tabwidth)
|
|
squeezer = self.make_squeezer_instance(editwin)
|
|
|
|
mock_count_lines = Mock(return_value=SENTINEL_VALUE)
|
|
text = 'TEXT'
|
|
with patch('idlelib.squeezer.count_lines_with_wrapping',
|
|
mock_count_lines):
|
|
self.assertIs(squeezer.count_lines(text), SENTINEL_VALUE)
|
|
mock_count_lines.assert_called_with(text, linewidth, tabwidth)
|
|
|
|
def test_init(self):
|
|
"""Test the creation of Squeezer instances."""
|
|
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):
|
|
"""Test Squeezer's overriding of the EditorWindow's write() method."""
|
|
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):
|
|
"""Test Squeezer's overriding of the EditorWindow's write() method."""
|
|
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):
|
|
"""Test Squeezer's overriding of the EditorWindow's write() method."""
|
|
editwin = self.make_mock_editor_window()
|
|
self._prepare_mock_editwin_for_count_lines(editwin,
|
|
linewidth=80, tabwidth=8)
|
|
|
|
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):
|
|
"""Test that the auto-squeezing creates an ExpandingButton properly."""
|
|
root = get_test_tk_root(self)
|
|
text_widget = Text(root)
|
|
text_widget.mark_set("iomark", "1.0")
|
|
|
|
editwin = self.make_mock_editor_window()
|
|
editwin.text = text_widget
|
|
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_event(self):
|
|
"""Test the squeeze_current_text event."""
|
|
root = get_test_tk_root(self)
|
|
|
|
# squeezing text should work for both stdout and stderr
|
|
for tag_name in ["stdout", "stderr"]:
|
|
text_widget = Text(root)
|
|
text_widget.mark_set("iomark", "1.0")
|
|
|
|
editwin = self.make_mock_editor_window()
|
|
editwin.text = editwin.per.bottom = text_widget
|
|
squeezer = self.make_squeezer_instance(editwin)
|
|
squeezer.count_lines = Mock(return_value=6)
|
|
|
|
# prepare some text in the Text widget
|
|
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)
|
|
|
|
# test squeezing the current text
|
|
retval = squeezer.squeeze_current_text_event(event=Mock())
|
|
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')
|
|
|
|
# test that expanding the squeezed text works and afterwards the
|
|
# Text widget contains the original text
|
|
squeezer.expandingbuttons[0].expand(event=Mock())
|
|
self.assertEqual(text_widget.get('1.0', 'end'), 'SOME\nTEXT\n\n')
|
|
self.assertEqual(len(squeezer.expandingbuttons), 0)
|
|
|
|
def test_squeeze_current_text_event_no_allowed_tags(self):
|
|
"""Test that the event doesn't squeeze text without a relevant tag."""
|
|
root = get_test_tk_root(self)
|
|
|
|
text_widget = Text(root)
|
|
text_widget.mark_set("iomark", "1.0")
|
|
|
|
editwin = self.make_mock_editor_window()
|
|
editwin.text = editwin.per.bottom = text_widget
|
|
squeezer = self.make_squeezer_instance(editwin)
|
|
squeezer.count_lines = Mock(return_value=6)
|
|
|
|
# prepare some text in the Text widget
|
|
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)
|
|
|
|
# test squeezing the current text
|
|
retval = squeezer.squeeze_current_text_event(event=Mock())
|
|
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):
|
|
"""Test squeezing text before existing squeezed text."""
|
|
root = get_test_tk_root(self)
|
|
|
|
text_widget = Text(root)
|
|
text_widget.mark_set("iomark", "1.0")
|
|
|
|
editwin = self.make_mock_editor_window()
|
|
editwin.text = editwin.per.bottom = text_widget
|
|
squeezer = self.make_squeezer_instance(editwin)
|
|
squeezer.count_lines = Mock(return_value=6)
|
|
|
|
# prepare some text in the Text widget and squeeze it
|
|
text_widget.insert("1.0", "SOME\nTEXT\n", "stdout")
|
|
text_widget.mark_set("insert", "1.0")
|
|
squeezer.squeeze_current_text_event(event=Mock())
|
|
self.assertEqual(len(squeezer.expandingbuttons), 1)
|
|
|
|
# test squeezing the current text
|
|
text_widget.insert("1.0", "MORE\nSTUFF\n", "stdout")
|
|
text_widget.mark_set("insert", "1.0")
|
|
retval = squeezer.squeeze_current_text_event(event=Mock())
|
|
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],
|
|
))
|
|
|
|
GetOptionSignature = namedtuple('GetOptionSignature',
|
|
'configType section option default type warn_on_default raw')
|
|
@classmethod
|
|
def _make_sig(cls, configType, section, option, default=sentinel.NOT_GIVEN,
|
|
type=sentinel.NOT_GIVEN,
|
|
warn_on_default=sentinel.NOT_GIVEN,
|
|
raw=sentinel.NOT_GIVEN):
|
|
return cls.GetOptionSignature(configType, section, option, default,
|
|
type, warn_on_default, raw)
|
|
|
|
@classmethod
|
|
def get_GetOption_signature(cls, mock_call_obj):
|
|
args, kwargs = mock_call_obj[-2:]
|
|
return cls._make_sig(*args, **kwargs)
|
|
|
|
def test_reload(self):
|
|
"""Test the reload() class-method."""
|
|
self.assertIsInstance(Squeezer.auto_squeeze_min_lines, int)
|
|
idleConf.SetOption('main', 'PyShell', 'auto-squeeze-min-lines', '42')
|
|
Squeezer.reload()
|
|
self.assertEqual(Squeezer.auto_squeeze_min_lines, 42)
|
|
|
|
|
|
class ExpandingButtonTest(unittest.TestCase):
|
|
"""Tests for the ExpandingButton class."""
|
|
# In these tests the squeezer instance is a mock, but actual tkinter
|
|
# Text and Button instances are created.
|
|
def make_mock_squeezer(self):
|
|
"""Helper for tests: Create a mock Squeezer object."""
|
|
root = get_test_tk_root(self)
|
|
squeezer = Mock()
|
|
squeezer.editwin.text = Text(root)
|
|
|
|
# Set default values for the configuration settings
|
|
squeezer.auto_squeeze_min_lines = 50
|
|
return squeezer
|
|
|
|
@patch('idlelib.squeezer.Hovertip', autospec=Hovertip)
|
|
def test_init(self, MockHovertip):
|
|
"""Test the simplest creation of an ExpandingButton."""
|
|
squeezer = self.make_mock_squeezer()
|
|
text_widget = squeezer.editwin.text
|
|
|
|
expandingbutton = ExpandingButton('TEXT', 'TAGS', 50, squeezer)
|
|
self.assertEqual(expandingbutton.s, 'TEXT')
|
|
|
|
# check that the underlying tkinter.Button is properly configured
|
|
self.assertEqual(expandingbutton.master, text_widget)
|
|
self.assertTrue('50 lines' in expandingbutton.cget('text'))
|
|
|
|
# check that the text widget still contains no text
|
|
self.assertEqual(text_widget.get('1.0', 'end'), '\n')
|
|
|
|
# check that the mouse events are bound
|
|
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())
|
|
|
|
# check that ToolTip was called once, with appropriate values
|
|
self.assertEqual(MockHovertip.call_count, 1)
|
|
MockHovertip.assert_called_with(expandingbutton, ANY, hover_delay=ANY)
|
|
|
|
# check that 'right-click' appears in the tooltip text
|
|
tooltip_text = MockHovertip.call_args[0][1]
|
|
self.assertIn('right-click', tooltip_text.lower())
|
|
|
|
def test_expand(self):
|
|
"""Test the expand event."""
|
|
squeezer = self.make_mock_squeezer()
|
|
expandingbutton = ExpandingButton('TEXT', 'TAGS', 50, squeezer)
|
|
|
|
# insert the button into the text widget
|
|
# (this is normally done by the Squeezer class)
|
|
text_widget = expandingbutton.text
|
|
text_widget.window_create("1.0", window=expandingbutton)
|
|
|
|
# set base_text to the text widget, so that changes are actually made
|
|
# to it (by ExpandingButton) and we can inspect these changes afterwards
|
|
expandingbutton.base_text = expandingbutton.text
|
|
|
|
# trigger the expand event
|
|
retval = expandingbutton.expand(event=Mock())
|
|
self.assertEqual(retval, None)
|
|
|
|
# check that the text was inserted into the text widget
|
|
self.assertEqual(text_widget.get('1.0', 'end'), 'TEXT\n')
|
|
|
|
# check that the 'TAGS' tag was set on the inserted text
|
|
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))
|
|
|
|
# check that the button removed itself from squeezer.expandingbuttons
|
|
self.assertEqual(squeezer.expandingbuttons.remove.call_count, 1)
|
|
squeezer.expandingbuttons.remove.assert_called_with(expandingbutton)
|
|
|
|
def test_expand_dangerous_oupput(self):
|
|
"""Test that expanding very long output asks user for confirmation."""
|
|
squeezer = self.make_mock_squeezer()
|
|
text = 'a' * 10**5
|
|
expandingbutton = ExpandingButton(text, 'TAGS', 50, squeezer)
|
|
expandingbutton.set_is_dangerous()
|
|
self.assertTrue(expandingbutton.is_dangerous)
|
|
|
|
# insert the button into the text widget
|
|
# (this is normally done by the Squeezer class)
|
|
text_widget = expandingbutton.text
|
|
text_widget.window_create("1.0", window=expandingbutton)
|
|
|
|
# set base_text to the text widget, so that changes are actually made
|
|
# to it (by ExpandingButton) and we can inspect these changes afterwards
|
|
expandingbutton.base_text = expandingbutton.text
|
|
|
|
# patch the message box module to always return False
|
|
with patch('idlelib.squeezer.tkMessageBox') as mock_msgbox:
|
|
mock_msgbox.askokcancel.return_value = False
|
|
mock_msgbox.askyesno.return_value = False
|
|
|
|
# trigger the expand event
|
|
retval = expandingbutton.expand(event=Mock())
|
|
|
|
# check that the event chain was broken and no text was inserted
|
|
self.assertEqual(retval, 'break')
|
|
self.assertEqual(expandingbutton.text.get('1.0', 'end-1c'), '')
|
|
|
|
# patch the message box module to always return True
|
|
with patch('idlelib.squeezer.tkMessageBox') as mock_msgbox:
|
|
mock_msgbox.askokcancel.return_value = True
|
|
mock_msgbox.askyesno.return_value = True
|
|
|
|
# trigger the expand event
|
|
retval = expandingbutton.expand(event=Mock())
|
|
|
|
# check that the event chain wasn't broken and the text was inserted
|
|
self.assertEqual(retval, None)
|
|
self.assertEqual(expandingbutton.text.get('1.0', 'end-1c'), text)
|
|
|
|
def test_copy(self):
|
|
"""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
|
|
squeezer = self.make_mock_squeezer()
|
|
expandingbutton = ExpandingButton('TEXT', 'TAGS', 50, squeezer)
|
|
expandingbutton.clipboard_clear = Mock()
|
|
expandingbutton.clipboard_append = Mock()
|
|
|
|
# trigger the copy event
|
|
retval = expandingbutton.copy(event=Mock())
|
|
self.assertEqual(retval, None)
|
|
|
|
# check that the expanding button called clipboard_clear() and
|
|
# clipboard_append('TEXT') once each
|
|
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):
|
|
"""Test the view event."""
|
|
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:
|
|
# trigger the view event
|
|
expandingbutton.view(event=Mock())
|
|
|
|
# check that the expanding button called view_text
|
|
self.assertEqual(mock_view_text.call_count, 1)
|
|
|
|
# check that the proper text was passed
|
|
self.assertEqual(mock_view_text.call_args[0][2], 'TEXT')
|
|
|
|
def test_rmenu(self):
|
|
"""Test the context menu."""
|
|
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)
|