Skip to content

Commit d694612

Browse files
serhiy-storchakaclaude
authored andcommitted
gh-151678: Add tests for the remaining tkinter widgets (GH-151687)
Cover previously-untested methods of several widgets: * Button, Checkbutton and Radiobutton: invoke, flash and toggle; * Entry: delete, icursor and the select_* aliases; * Spinbox: invoke, identify and scan; * Scale and Scrollbar: identify, and Scrollbar fraction and delta; * PanedWindow: panes, remove/forget, sash and proxy positioning, identify, and adding panes with configuration options. Also test that invoke does nothing for a disabled button and the errors raised for invalid indices, coordinates, option names and values. (cherry picked from commit 93b9e76) Co-authored-by: Serhiy Storchaka <storchaka@gmail.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 1495415 commit d694612

1 file changed

Lines changed: 250 additions & 0 deletions

File tree

Lib/test/test_tkinter/test_widgets.py

Lines changed: 250 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,23 @@ def test_configure_default(self):
208208
widget = self.create()
209209
self.checkEnumParam(widget, 'default', 'active', 'disabled', 'normal')
210210

211+
def test_invoke(self):
212+
success = []
213+
widget = self.create(command=lambda: success.append(1))
214+
widget.pack()
215+
widget.invoke()
216+
self.assertEqual(success, [1])
217+
# invoke does nothing for a disabled button.
218+
widget.configure(state='disabled')
219+
widget.invoke()
220+
self.assertEqual(success, [1])
221+
222+
def test_flash(self):
223+
widget = self.create()
224+
widget.pack()
225+
widget.update_idletasks()
226+
widget.flash() # No exception.
227+
211228

212229
@add_configure_tests(StandardOptionsTests)
213230
class CheckbuttonTest(AbstractLabelTest, unittest.TestCase):
@@ -263,6 +280,39 @@ def test_same_name(self):
263280
b2.deselect()
264281
self.assertEqual(v.get(), 0)
265282

283+
def test_invoke(self):
284+
success = []
285+
v = tkinter.IntVar(self.root)
286+
widget = self.create(variable=v, onvalue=1, offvalue=0,
287+
command=lambda: success.append(v.get()))
288+
widget.pack()
289+
widget.invoke()
290+
self.assertEqual(v.get(), 1)
291+
self.assertEqual(success, [1])
292+
widget.invoke()
293+
self.assertEqual(v.get(), 0)
294+
self.assertEqual(success, [1, 0])
295+
# A disabled checkbutton is not toggled and its command is not called.
296+
widget.configure(state='disabled')
297+
widget.invoke()
298+
self.assertEqual(v.get(), 0)
299+
self.assertEqual(success, [1, 0])
300+
301+
def test_toggle(self):
302+
v = tkinter.IntVar(self.root)
303+
widget = self.create(variable=v, onvalue=1, offvalue=0)
304+
self.assertEqual(v.get(), 0)
305+
widget.toggle()
306+
self.assertEqual(v.get(), 1)
307+
widget.toggle()
308+
self.assertEqual(v.get(), 0)
309+
310+
def test_flash(self):
311+
widget = self.create()
312+
widget.pack()
313+
widget.update_idletasks()
314+
widget.flash() # No exception.
315+
266316
@add_configure_tests(StandardOptionsTests)
267317
class RadiobuttonTest(AbstractLabelTest, unittest.TestCase):
268318
OPTIONS = (
@@ -285,6 +335,28 @@ def test_configure_value(self):
285335
widget = self.create()
286336
self.checkParams(widget, 'value', 1, 2.3, '', 'any string')
287337

338+
def test_invoke(self):
339+
success = []
340+
v = tkinter.StringVar(self.root)
341+
widget = self.create(variable=v, value='on',
342+
command=lambda: success.append(v.get()))
343+
widget.pack()
344+
widget.invoke()
345+
self.assertEqual(v.get(), 'on')
346+
self.assertEqual(success, ['on'])
347+
# invoke does nothing for a disabled radiobutton.
348+
v.set('')
349+
widget.configure(state='disabled')
350+
widget.invoke()
351+
self.assertEqual(v.get(), '')
352+
self.assertEqual(success, ['on'])
353+
354+
def test_flash(self):
355+
widget = self.create()
356+
widget.pack()
357+
widget.update_idletasks()
358+
widget.flash() # No exception.
359+
288360

289361
@add_configure_tests(StandardOptionsTests)
290362
class MenubuttonTest(AbstractLabelTest, unittest.TestCase):
@@ -476,6 +548,47 @@ def test_selection_methods(self):
476548
self.assertEqual(widget.selection_get(), '12345')
477549
widget.selection_adjust(0)
478550

551+
def test_delete(self):
552+
widget = self.create()
553+
widget.insert(0, 'abcdef')
554+
widget.delete(1, 3)
555+
self.assertEqual(widget.get(), 'adef')
556+
widget.delete(1)
557+
self.assertEqual(widget.get(), 'aef')
558+
widget.delete(0, 'end')
559+
self.assertEqual(widget.get(), '')
560+
self.assertRaisesRegex(TclError, r'bad (entry|spinbox) index "xyz"',
561+
widget.delete, 'xyz')
562+
self.assertRaises(TypeError, widget.delete)
563+
564+
def test_icursor(self):
565+
widget = self.create()
566+
widget.insert(0, 'abcdef')
567+
widget.icursor(3)
568+
widget.insert('insert', 'XYZ')
569+
self.assertEqual(widget.get(), 'abcXYZdef')
570+
self.assertRaisesRegex(TclError, r'bad (entry|spinbox) index "xyz"',
571+
widget.icursor, 'xyz')
572+
self.assertRaises(TypeError, widget.icursor)
573+
574+
def test_select_aliases(self):
575+
# The select_* methods are aliases of the selection_* methods.
576+
widget = self.create()
577+
widget.insert(0, '12345')
578+
self.assertFalse(widget.select_present())
579+
widget.select_range(0, 'end')
580+
self.assertTrue(widget.select_present())
581+
self.assertEqual(widget.selection_get(), '12345')
582+
widget.select_from(1)
583+
widget.select_to(3)
584+
self.assertEqual(widget.selection_get(), '23')
585+
widget.select_adjust(4)
586+
self.assertEqual(widget.selection_get(), '234')
587+
widget.select_clear()
588+
self.assertFalse(widget.select_present())
589+
self.assertRaisesRegex(TclError, 'bad entry index "xyz"',
590+
widget.select_range, 'xyz', 'end')
591+
479592

480593
@add_configure_tests(StandardOptionsTests)
481594
class SpinboxTest(EntryTest, unittest.TestCase):
@@ -624,6 +737,38 @@ def test_selection_element(self):
624737
widget.selection_element("buttondown")
625738
self.assertEqual(widget.selection_element(), "buttondown")
626739

740+
# Spinbox has no select_* aliases, unlike Entry.
741+
test_select_aliases = None
742+
743+
def test_invoke(self):
744+
widget = self.create(from_=0, to=10)
745+
widget.delete(0, 'end')
746+
widget.insert(0, '5')
747+
widget.invoke('buttonup')
748+
self.assertEqual(widget.get(), '6')
749+
widget.invoke('buttondown')
750+
self.assertEqual(widget.get(), '5')
751+
self.assertRaisesRegex(TclError, 'bad element "spam"',
752+
widget.invoke, 'spam')
753+
754+
def test_identify(self):
755+
widget = self.create()
756+
widget.pack()
757+
widget.update_idletasks()
758+
# The empty string is returned for a point over no element.
759+
self.assertIn(widget.identify(5, 5),
760+
('entry', 'buttonup', 'buttondown', 'none', ''))
761+
self.assertRaises(TclError, widget.identify, 'a', 'b')
762+
763+
def test_scan(self):
764+
widget = self.create()
765+
widget.insert(0, 'a' * 100)
766+
widget.pack()
767+
widget.update_idletasks()
768+
self.assertEqual(widget.scan_mark(10), ())
769+
self.assertEqual(widget.scan_dragto(0), ())
770+
self.assertRaises(TypeError, widget.scan_mark)
771+
627772

628773
@add_configure_tests(StandardOptionsTests)
629774
class TextTest(AbstractWidgetTest, unittest.TestCase):
@@ -1349,6 +1494,14 @@ def test_configure_to(self):
13491494
self.checkFloatParam(widget, 'to', 300, 14.9, 15.1, -10,
13501495
conv=float_round)
13511496

1497+
def test_identify(self):
1498+
widget = self.create()
1499+
widget.pack()
1500+
widget.update_idletasks()
1501+
self.assertIn(widget.identify(5, 5),
1502+
('slider', 'trough1', 'trough2', ''))
1503+
self.assertRaises(TclError, widget.identify, 'a', 'b')
1504+
13521505

13531506
@add_configure_tests(PixelSizeTests, StandardOptionsTests)
13541507
class ScrollbarTest(AbstractWidgetTest, unittest.TestCase):
@@ -1402,6 +1555,34 @@ def test_set(self):
14021555
self.assertRaises(TypeError, sb.set, 0.6)
14031556
self.assertRaises(TypeError, sb.set, 0.6, 0.7, 0.8)
14041557

1558+
def test_fraction(self):
1559+
sb = self.create()
1560+
sb.pack(fill='y', expand=True)
1561+
sb.update_idletasks()
1562+
self.assertIsInstance(sb.fraction(0, 0), float)
1563+
f = sb.fraction(0, 1000)
1564+
self.assertIsInstance(f, float)
1565+
self.assertGreaterEqual(f, 0.0)
1566+
self.assertLessEqual(f, 1.0)
1567+
self.assertRaises(TclError, sb.fraction, 'a', 'b')
1568+
self.assertRaises(TypeError, sb.fraction, 0)
1569+
1570+
def test_delta(self):
1571+
sb = self.create()
1572+
sb.pack(fill='y', expand=True)
1573+
sb.update_idletasks()
1574+
self.assertIsInstance(sb.delta(0, 10), float)
1575+
self.assertRaises(TclError, sb.delta, 'a', 'b')
1576+
self.assertRaises(TypeError, sb.delta, 0)
1577+
1578+
def test_identify(self):
1579+
sb = self.create()
1580+
sb.pack(fill='y', expand=True)
1581+
sb.update_idletasks()
1582+
self.assertIn(sb.identify(5, 5),
1583+
('arrow1', 'arrow2', 'slider', 'trough1', 'trough2', ''))
1584+
self.assertRaises(TclError, sb.identify, 'a', 'b')
1585+
14051586

14061587
@add_configure_tests(PixelSizeTests, StandardOptionsTests)
14071588
class PanedWindowTest(AbstractWidgetTest, unittest.TestCase):
@@ -1479,6 +1660,75 @@ def create2(self):
14791660
p.add(c)
14801661
return p, b, c
14811662

1663+
def test_panes(self):
1664+
p, b, c = self.create2()
1665+
self.assertEqual([str(x) for x in p.panes()], [str(b), str(c)])
1666+
1667+
def test_remove(self):
1668+
p, b, c = self.create2()
1669+
p.remove(b)
1670+
self.assertEqual([str(x) for x in p.panes()], [str(c)])
1671+
p.forget(c) # forget is an alias of remove.
1672+
self.assertEqual(p.panes(), ())
1673+
1674+
def test_sash(self):
1675+
p, b, c = self.create2()
1676+
p.configure(width=200, height=50)
1677+
p.pack()
1678+
p.update()
1679+
x, y = p.sash_coord(0)
1680+
self.assertIsInstance(x, int)
1681+
self.assertIsInstance(y, int)
1682+
p.sash_place(0, 120, 0)
1683+
p.update()
1684+
self.assertEqual(p.sash_coord(0)[0], 120)
1685+
p.sash_mark(0) # No exception.
1686+
self.assertRaises(TclError, p.sash_coord, 5)
1687+
1688+
def test_proxy(self):
1689+
p, b, c = self.create2()
1690+
p.configure(width=200, height=50)
1691+
p.pack()
1692+
p.update()
1693+
p.proxy_place(100, 10)
1694+
p.update()
1695+
self.assertEqual(p.proxy_coord()[0], 100)
1696+
p.proxy_forget()
1697+
p.update()
1698+
1699+
def test_identify(self):
1700+
p, b, c = self.create2()
1701+
p.configure(width=200, height=50)
1702+
p.pack()
1703+
p.update()
1704+
x, y = p.sash_coord(0)
1705+
# A point over the sash reports the sash.
1706+
self.assertIn('sash', p.identify(x + 1, y + 5))
1707+
# A point over a pane reports nothing.
1708+
self.assertFalse(p.identify(2, 2))
1709+
self.assertRaises(TclError, p.identify, 'a', 'b')
1710+
1711+
def test_add_options(self):
1712+
p = self.create()
1713+
b = tkinter.Button(p)
1714+
p.add(b, minsize=40, padx=3, sticky='ns')
1715+
self.assertEqual(p.panecget(b, 'minsize'),
1716+
40 if self.wantobjects else '40')
1717+
self.assertEqual(p.panecget(b, 'padx'),
1718+
3 if self.wantobjects else '3')
1719+
self.assertEqual(p.panecget(b, 'sticky'), 'ns')
1720+
self.assertRaisesRegex(TclError, 'unknown option "-spam"',
1721+
p.add, tkinter.Button(p), spam='x')
1722+
self.assertRaisesRegex(TclError, 'bad window path name "spam"',
1723+
p.add, 'spam')
1724+
1725+
def test_paneconfigure_errors(self):
1726+
p, b, c = self.create2()
1727+
self.assertRaisesRegex(TclError, 'unknown option "-spam"',
1728+
p.paneconfigure, b, spam='x')
1729+
self.assertRaises(TclError, p.panecget, b, 'spam')
1730+
self.assertRaises(TclError, p.paneconfigure, 'spam')
1731+
14821732
def test_paneconfigure(self):
14831733
p, b, c = self.create2()
14841734
self.assertRaises(TypeError, p.paneconfigure)

0 commit comments

Comments
 (0)