Skip to content

Commit a8252d5

Browse files
[3.13] gh-151678: Add tests for the remaining tkinter widgets (GH-151687) (GH-151727)
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: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent e514b4a commit a8252d5

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
@@ -186,6 +186,23 @@ def test_configure_default(self):
186186
widget = self.create()
187187
self.checkEnumParam(widget, 'default', 'active', 'disabled', 'normal')
188188

189+
def test_invoke(self):
190+
success = []
191+
widget = self.create(command=lambda: success.append(1))
192+
widget.pack()
193+
widget.invoke()
194+
self.assertEqual(success, [1])
195+
# invoke does nothing for a disabled button.
196+
widget.configure(state='disabled')
197+
widget.invoke()
198+
self.assertEqual(success, [1])
199+
200+
def test_flash(self):
201+
widget = self.create()
202+
widget.pack()
203+
widget.update_idletasks()
204+
widget.flash() # No exception.
205+
189206

190207
@add_standard_options(StandardOptionsTests)
191208
class CheckbuttonTest(AbstractLabelTest, unittest.TestCase):
@@ -241,6 +258,39 @@ def test_same_name(self):
241258
b2.deselect()
242259
self.assertEqual(v.get(), 0)
243260

261+
def test_invoke(self):
262+
success = []
263+
v = tkinter.IntVar(self.root)
264+
widget = self.create(variable=v, onvalue=1, offvalue=0,
265+
command=lambda: success.append(v.get()))
266+
widget.pack()
267+
widget.invoke()
268+
self.assertEqual(v.get(), 1)
269+
self.assertEqual(success, [1])
270+
widget.invoke()
271+
self.assertEqual(v.get(), 0)
272+
self.assertEqual(success, [1, 0])
273+
# A disabled checkbutton is not toggled and its command is not called.
274+
widget.configure(state='disabled')
275+
widget.invoke()
276+
self.assertEqual(v.get(), 0)
277+
self.assertEqual(success, [1, 0])
278+
279+
def test_toggle(self):
280+
v = tkinter.IntVar(self.root)
281+
widget = self.create(variable=v, onvalue=1, offvalue=0)
282+
self.assertEqual(v.get(), 0)
283+
widget.toggle()
284+
self.assertEqual(v.get(), 1)
285+
widget.toggle()
286+
self.assertEqual(v.get(), 0)
287+
288+
def test_flash(self):
289+
widget = self.create()
290+
widget.pack()
291+
widget.update_idletasks()
292+
widget.flash() # No exception.
293+
244294

245295
@add_standard_options(StandardOptionsTests)
246296
class RadiobuttonTest(AbstractLabelTest, unittest.TestCase):
@@ -264,6 +314,28 @@ def test_configure_value(self):
264314
widget = self.create()
265315
self.checkParams(widget, 'value', 1, 2.3, '', 'any string')
266316

317+
def test_invoke(self):
318+
success = []
319+
v = tkinter.StringVar(self.root)
320+
widget = self.create(variable=v, value='on',
321+
command=lambda: success.append(v.get()))
322+
widget.pack()
323+
widget.invoke()
324+
self.assertEqual(v.get(), 'on')
325+
self.assertEqual(success, ['on'])
326+
# invoke does nothing for a disabled radiobutton.
327+
v.set('')
328+
widget.configure(state='disabled')
329+
widget.invoke()
330+
self.assertEqual(v.get(), '')
331+
self.assertEqual(success, ['on'])
332+
333+
def test_flash(self):
334+
widget = self.create()
335+
widget.pack()
336+
widget.update_idletasks()
337+
widget.flash() # No exception.
338+
267339

268340
@add_standard_options(StandardOptionsTests)
269341
class MenubuttonTest(AbstractLabelTest, unittest.TestCase):
@@ -422,6 +494,47 @@ def test_selection_methods(self):
422494
self.assertEqual(widget.selection_get(), '12345')
423495
widget.selection_adjust(0)
424496

497+
def test_delete(self):
498+
widget = self.create()
499+
widget.insert(0, 'abcdef')
500+
widget.delete(1, 3)
501+
self.assertEqual(widget.get(), 'adef')
502+
widget.delete(1)
503+
self.assertEqual(widget.get(), 'aef')
504+
widget.delete(0, 'end')
505+
self.assertEqual(widget.get(), '')
506+
self.assertRaisesRegex(TclError, r'bad (entry|spinbox) index "xyz"',
507+
widget.delete, 'xyz')
508+
self.assertRaises(TypeError, widget.delete)
509+
510+
def test_icursor(self):
511+
widget = self.create()
512+
widget.insert(0, 'abcdef')
513+
widget.icursor(3)
514+
widget.insert('insert', 'XYZ')
515+
self.assertEqual(widget.get(), 'abcXYZdef')
516+
self.assertRaisesRegex(TclError, r'bad (entry|spinbox) index "xyz"',
517+
widget.icursor, 'xyz')
518+
self.assertRaises(TypeError, widget.icursor)
519+
520+
def test_select_aliases(self):
521+
# The select_* methods are aliases of the selection_* methods.
522+
widget = self.create()
523+
widget.insert(0, '12345')
524+
self.assertFalse(widget.select_present())
525+
widget.select_range(0, 'end')
526+
self.assertTrue(widget.select_present())
527+
self.assertEqual(widget.selection_get(), '12345')
528+
widget.select_from(1)
529+
widget.select_to(3)
530+
self.assertEqual(widget.selection_get(), '23')
531+
widget.select_adjust(4)
532+
self.assertEqual(widget.selection_get(), '234')
533+
widget.select_clear()
534+
self.assertFalse(widget.select_present())
535+
self.assertRaisesRegex(TclError, 'bad entry index "xyz"',
536+
widget.select_range, 'xyz', 'end')
537+
425538

426539
@add_standard_options(StandardOptionsTests)
427540
class SpinboxTest(EntryTest, unittest.TestCase):
@@ -559,6 +672,38 @@ def test_selection_element(self):
559672
widget.selection_element("buttondown")
560673
self.assertEqual(widget.selection_element(), "buttondown")
561674

675+
# Spinbox has no select_* aliases, unlike Entry.
676+
test_select_aliases = None
677+
678+
def test_invoke(self):
679+
widget = self.create(from_=0, to=10)
680+
widget.delete(0, 'end')
681+
widget.insert(0, '5')
682+
widget.invoke('buttonup')
683+
self.assertEqual(widget.get(), '6')
684+
widget.invoke('buttondown')
685+
self.assertEqual(widget.get(), '5')
686+
self.assertRaisesRegex(TclError, 'bad element "spam"',
687+
widget.invoke, 'spam')
688+
689+
def test_identify(self):
690+
widget = self.create()
691+
widget.pack()
692+
widget.update_idletasks()
693+
# The empty string is returned for a point over no element.
694+
self.assertIn(widget.identify(5, 5),
695+
('entry', 'buttonup', 'buttondown', 'none', ''))
696+
self.assertRaises(TclError, widget.identify, 'a', 'b')
697+
698+
def test_scan(self):
699+
widget = self.create()
700+
widget.insert(0, 'a' * 100)
701+
widget.pack()
702+
widget.update_idletasks()
703+
self.assertEqual(widget.scan_mark(10), ())
704+
self.assertEqual(widget.scan_dragto(0), ())
705+
self.assertRaises(TypeError, widget.scan_mark)
706+
562707

563708
@add_standard_options(StandardOptionsTests)
564709
class TextTest(AbstractWidgetTest, unittest.TestCase):
@@ -1259,6 +1404,14 @@ def test_configure_to(self):
12591404
self.checkFloatParam(widget, 'to', 300, 14.9, 15.1, -10,
12601405
conv=float_round)
12611406

1407+
def test_identify(self):
1408+
widget = self.create()
1409+
widget.pack()
1410+
widget.update_idletasks()
1411+
self.assertIn(widget.identify(5, 5),
1412+
('slider', 'trough1', 'trough2', ''))
1413+
self.assertRaises(TclError, widget.identify, 'a', 'b')
1414+
12621415

12631416
@add_standard_options(PixelSizeTests, StandardOptionsTests)
12641417
class ScrollbarTest(AbstractWidgetTest, unittest.TestCase):
@@ -1308,6 +1461,34 @@ def test_set(self):
13081461
self.assertRaises(TypeError, sb.set, 0.6)
13091462
self.assertRaises(TypeError, sb.set, 0.6, 0.7, 0.8)
13101463

1464+
def test_fraction(self):
1465+
sb = self.create()
1466+
sb.pack(fill='y', expand=True)
1467+
sb.update_idletasks()
1468+
self.assertIsInstance(sb.fraction(0, 0), float)
1469+
f = sb.fraction(0, 1000)
1470+
self.assertIsInstance(f, float)
1471+
self.assertGreaterEqual(f, 0.0)
1472+
self.assertLessEqual(f, 1.0)
1473+
self.assertRaises(TclError, sb.fraction, 'a', 'b')
1474+
self.assertRaises(TypeError, sb.fraction, 0)
1475+
1476+
def test_delta(self):
1477+
sb = self.create()
1478+
sb.pack(fill='y', expand=True)
1479+
sb.update_idletasks()
1480+
self.assertIsInstance(sb.delta(0, 10), float)
1481+
self.assertRaises(TclError, sb.delta, 'a', 'b')
1482+
self.assertRaises(TypeError, sb.delta, 0)
1483+
1484+
def test_identify(self):
1485+
sb = self.create()
1486+
sb.pack(fill='y', expand=True)
1487+
sb.update_idletasks()
1488+
self.assertIn(sb.identify(5, 5),
1489+
('arrow1', 'arrow2', 'slider', 'trough1', 'trough2', ''))
1490+
self.assertRaises(TclError, sb.identify, 'a', 'b')
1491+
13111492

13121493
@add_standard_options(StandardOptionsTests)
13131494
class PanedWindowTest(AbstractWidgetTest, unittest.TestCase):
@@ -1395,6 +1576,75 @@ def create2(self):
13951576
p.add(c)
13961577
return p, b, c
13971578

1579+
def test_panes(self):
1580+
p, b, c = self.create2()
1581+
self.assertEqual([str(x) for x in p.panes()], [str(b), str(c)])
1582+
1583+
def test_remove(self):
1584+
p, b, c = self.create2()
1585+
p.remove(b)
1586+
self.assertEqual([str(x) for x in p.panes()], [str(c)])
1587+
p.forget(c) # forget is an alias of remove.
1588+
self.assertEqual(p.panes(), ())
1589+
1590+
def test_sash(self):
1591+
p, b, c = self.create2()
1592+
p.configure(width=200, height=50)
1593+
p.pack()
1594+
p.update()
1595+
x, y = p.sash_coord(0)
1596+
self.assertIsInstance(x, int)
1597+
self.assertIsInstance(y, int)
1598+
p.sash_place(0, 120, 0)
1599+
p.update()
1600+
self.assertEqual(p.sash_coord(0)[0], 120)
1601+
p.sash_mark(0) # No exception.
1602+
self.assertRaises(TclError, p.sash_coord, 5)
1603+
1604+
def test_proxy(self):
1605+
p, b, c = self.create2()
1606+
p.configure(width=200, height=50)
1607+
p.pack()
1608+
p.update()
1609+
p.proxy_place(100, 10)
1610+
p.update()
1611+
self.assertEqual(p.proxy_coord()[0], 100)
1612+
p.proxy_forget()
1613+
p.update()
1614+
1615+
def test_identify(self):
1616+
p, b, c = self.create2()
1617+
p.configure(width=200, height=50)
1618+
p.pack()
1619+
p.update()
1620+
x, y = p.sash_coord(0)
1621+
# A point over the sash reports the sash.
1622+
self.assertIn('sash', p.identify(x + 1, y + 5))
1623+
# A point over a pane reports nothing.
1624+
self.assertFalse(p.identify(2, 2))
1625+
self.assertRaises(TclError, p.identify, 'a', 'b')
1626+
1627+
def test_add_options(self):
1628+
p = self.create()
1629+
b = tkinter.Button(p)
1630+
p.add(b, minsize=40, padx=3, sticky='ns')
1631+
self.assertEqual(p.panecget(b, 'minsize'),
1632+
40 if self.wantobjects else '40')
1633+
self.assertEqual(p.panecget(b, 'padx'),
1634+
3 if self.wantobjects else '3')
1635+
self.assertEqual(p.panecget(b, 'sticky'), 'ns')
1636+
self.assertRaisesRegex(TclError, 'unknown option "-spam"',
1637+
p.add, tkinter.Button(p), spam='x')
1638+
self.assertRaisesRegex(TclError, 'bad window path name "spam"',
1639+
p.add, 'spam')
1640+
1641+
def test_paneconfigure_errors(self):
1642+
p, b, c = self.create2()
1643+
self.assertRaisesRegex(TclError, 'unknown option "-spam"',
1644+
p.paneconfigure, b, spam='x')
1645+
self.assertRaises(TclError, p.panecget, b, 'spam')
1646+
self.assertRaises(TclError, p.paneconfigure, 'spam')
1647+
13981648
def test_paneconfigure(self):
13991649
p, b, c = self.create2()
14001650
self.assertRaises(TypeError, p.paneconfigure)

0 commit comments

Comments
 (0)