Skip to content

Commit f9c55d8

Browse files
gh-151678: Add tests for tkinter.Menu
Cover previously-untested Menu methods in MenuTest: adding, inserting and deleting items of every type, index resolution, invoking items, entry x/y positions, and post/unpost/tk_popup mapping. Also test per-entry configuration options and the errors raised for invalid indices, entry types, option names and option values. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent bfecfcc commit f9c55d8

1 file changed

Lines changed: 179 additions & 0 deletions

File tree

Lib/test/test_tkinter/test_widgets.py

Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1542,6 +1542,185 @@ def test_entryconfigure_variable(self):
15421542
m1.entryconfigure(1, variable=v2)
15431543
self.assertEqual(str(m1.entrycget(1, 'variable')), str(v2))
15441544

1545+
def test_add(self):
1546+
m = self.create(tearoff=False)
1547+
m.add_command(label='Command')
1548+
m.add_checkbutton(label='Checkbutton')
1549+
m.add_radiobutton(label='Radiobutton')
1550+
m.add_separator()
1551+
m.add_cascade(label='Cascade', menu=tkinter.Menu(m, tearoff=False))
1552+
self.assertEqual(m.index('end'), 4)
1553+
self.assertEqual([m.type(i) for i in range(5)],
1554+
['command', 'checkbutton', 'radiobutton',
1555+
'separator', 'cascade'])
1556+
self.assertEqual(m.entrycget(0, 'label'), 'Command')
1557+
self.assertRaisesRegex(TclError, 'bad menu entry type "spam"',
1558+
m.add, 'spam')
1559+
1560+
def test_insert(self):
1561+
m = self.create(tearoff=False)
1562+
m.add_command(label='A')
1563+
m.add_command(label='C')
1564+
m.insert_command(1, label='B')
1565+
m.insert_separator(0)
1566+
m.insert_checkbutton('end', label='D')
1567+
m.insert_radiobutton(0, label='top')
1568+
m.insert_cascade(2, label='sub',
1569+
menu=tkinter.Menu(m, tearoff=False))
1570+
self.assertEqual(
1571+
[m.type(i) for i in range(m.index('end') + 1)],
1572+
['radiobutton', 'separator', 'cascade', 'command',
1573+
'command', 'command', 'checkbutton'])
1574+
self.assertEqual(
1575+
[m.entrycget(i, 'label') for i in (3, 4, 5)],
1576+
['A', 'B', 'C'])
1577+
self.assertRaisesRegex(TclError, 'bad menu entry type "spam"',
1578+
m.insert, 0, 'spam')
1579+
self.assertRaisesRegex(TclError, 'bad menu entry index "spam"',
1580+
m.insert_command, 'spam', label='z')
1581+
1582+
def test_delete(self):
1583+
m = self.create(tearoff=False)
1584+
commands = []
1585+
for label in 'ABCDE':
1586+
m.add_command(label=label,
1587+
command=lambda label=label: commands.append(label))
1588+
# The Tcl command for a deleted item is cleaned up.
1589+
funcid = str(m.entrycget(2, 'command'))
1590+
self.assertEqual(
1591+
m.tk.splitlist(m.tk.call('info', 'commands', funcid)), (funcid,))
1592+
1593+
m.delete(2) # Delete a single item ('C').
1594+
self.assertEqual([m.entrycget(i, 'label') for i in range(4)],
1595+
['A', 'B', 'D', 'E'])
1596+
self.assertEqual(
1597+
m.tk.splitlist(m.tk.call('info', 'commands', funcid)), ())
1598+
1599+
m.delete(1, 2) # Delete a range ('B' and 'D').
1600+
self.assertEqual([m.entrycget(i, 'label') for i in range(2)],
1601+
['A', 'E'])
1602+
self.assertRaises(TypeError, m.delete)
1603+
1604+
def test_index(self):
1605+
m = self.create(tearoff=False)
1606+
self.assertIsNone(m.index('end'))
1607+
m.add_command(label='First')
1608+
m.add_command(label='Second')
1609+
self.assertEqual(m.index('end'), 1)
1610+
self.assertEqual(m.index('last'), 1)
1611+
self.assertEqual(m.index('Second'), 1)
1612+
self.assertEqual(m.index(0), 0)
1613+
# 'active' and 'none' map to None when no item is active.
1614+
self.assertIsNone(m.index('active'))
1615+
self.assertIsNone(m.index('none'))
1616+
self.assertRaisesRegex(TclError, 'bad menu entry index "spam"',
1617+
m.index, 'spam')
1618+
1619+
def test_invoke(self):
1620+
m = self.create(tearoff=False)
1621+
commands = []
1622+
m.add_command(label='Command',
1623+
command=lambda: commands.append('invoked'))
1624+
var = tkinter.IntVar(self.root)
1625+
m.add_checkbutton(label='Check', variable=var,
1626+
onvalue=1, offvalue=0)
1627+
rvar = tkinter.StringVar(self.root)
1628+
m.add_radiobutton(label='Radio', variable=rvar, value='on')
1629+
1630+
m.invoke(0)
1631+
self.assertEqual(commands, ['invoked'])
1632+
m.invoke(1)
1633+
self.assertEqual(var.get(), 1)
1634+
m.invoke(1)
1635+
self.assertEqual(var.get(), 0)
1636+
m.invoke(2)
1637+
self.assertEqual(rvar.get(), 'on')
1638+
self.assertRaisesRegex(TclError, 'bad menu entry index "spam"',
1639+
m.invoke, 'spam')
1640+
1641+
def test_xposition_yposition(self):
1642+
m = self.create(tearoff=False)
1643+
m.add_command(label='First')
1644+
m.add_command(label='Second')
1645+
m.update_idletasks()
1646+
self.assertIsInstance(m.xposition(0), int)
1647+
y0 = m.yposition(0)
1648+
y1 = m.yposition(1)
1649+
self.assertIsInstance(y0, int)
1650+
self.assertLess(y0, y1)
1651+
# An out-of-range index gives the position past the last item.
1652+
self.assertEqual(m.xposition('end'), m.xposition(1))
1653+
self.assertRaisesRegex(TclError, 'bad menu entry index "spam"',
1654+
m.xposition, 'spam')
1655+
self.assertRaisesRegex(TclError, 'bad menu entry index "spam"',
1656+
m.yposition, 'spam')
1657+
1658+
def test_post_unpost(self):
1659+
m = self.create(tearoff=False)
1660+
m.add_command(label='First')
1661+
m.add_command(label='Second')
1662+
self.assertFalse(m.winfo_ismapped())
1663+
1664+
m.post(0, 0)
1665+
m.update()
1666+
self.assertTrue(m.winfo_ismapped())
1667+
m.unpost()
1668+
m.update()
1669+
self.assertFalse(m.winfo_ismapped())
1670+
1671+
m.tk_popup(0, 0)
1672+
m.update()
1673+
self.assertTrue(m.winfo_ismapped())
1674+
m.unpost()
1675+
m.update()
1676+
self.assertFalse(m.winfo_ismapped())
1677+
1678+
def check_entry_option(self, m, index, option, value, expected=None):
1679+
if expected is None:
1680+
expected = value
1681+
m.entryconfigure(index, **{option: value})
1682+
self.assertEqual(str(m.entrycget(index, option)), str(expected))
1683+
self.assertEqual(str(m.entryconfigure(index, option)[4]), str(expected))
1684+
1685+
def test_entry_options(self):
1686+
m = self.create(tearoff=False)
1687+
m.add_command(label='Command')
1688+
self.check_entry_option(m, 0, 'accelerator', 'Ctrl+O')
1689+
self.check_entry_option(m, 0, 'underline', 2)
1690+
self.check_entry_option(m, 0, 'state', 'disabled')
1691+
self.check_entry_option(m, 0, 'background', 'red')
1692+
self.check_entry_option(m, 0, 'foreground', 'blue')
1693+
self.check_entry_option(m, 0, 'columnbreak', 1)
1694+
self.check_entry_option(m, 0, 'hidemargin', 1)
1695+
1696+
m.add_checkbutton(label='Checkbutton')
1697+
self.check_entry_option(m, 1, 'onvalue', 'Y')
1698+
self.check_entry_option(m, 1, 'offvalue', 'N')
1699+
self.check_entry_option(m, 1, 'indicatoron', 0)
1700+
1701+
m.add_radiobutton(label='Radiobutton')
1702+
self.check_entry_option(m, 2, 'value', 'V')
1703+
self.check_entry_option(m, 2, 'selectcolor', 'green')
1704+
1705+
def test_entry_options_invalid(self):
1706+
m = self.create(tearoff=False)
1707+
m.add_command(label='Command')
1708+
self.assertRaisesRegex(TclError, 'unknown option "-spam"',
1709+
m.entrycget, 0, 'spam')
1710+
self.assertRaisesRegex(TclError, 'unknown option "-spam"',
1711+
m.entryconfigure, 0, spam='x')
1712+
self.assertRaisesRegex(TclError, 'bad state "spam"',
1713+
m.entryconfigure, 0, state='spam')
1714+
self.assertRaisesRegex(TclError, 'expected integer but got "spam"',
1715+
m.entryconfigure, 0, underline='spam')
1716+
self.assertRaisesRegex(TclError, 'unknown color name "spam"',
1717+
m.entryconfigure, 0, background='spam')
1718+
self.assertRaisesRegex(TclError, 'expected boolean value but got "spam"',
1719+
m.entryconfigure, 0, columnbreak='spam')
1720+
# onvalue applies only to checkbutton and radiobutton entries.
1721+
self.assertRaisesRegex(TclError, 'unknown option "-onvalue"',
1722+
m.entrycget, 0, 'onvalue')
1723+
15451724

15461725
@add_configure_tests(PixelSizeTests, StandardOptionsTests)
15471726
class MessageTest(AbstractWidgetTest, unittest.TestCase):

0 commit comments

Comments
 (0)