From: Serhiy Storchaka Date: Fri, 19 Jun 2026 15:45:39 +0000 (+0300) Subject: gh-151678: Add tests for tkinter.ttk methods (GH-151736) X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;p=thirdparty%2FPython%2Fcpython.git gh-151678: Add tests for tkinter.ttk methods (GH-151736) Cover previously-untested ttk methods: * Progressbar.step, start and stop; * Treeview.parent, next, prev, see and identify_element; * Style.theme_settings; * OptionMenu.set_menu. Co-authored-by: Claude Opus 4.8 (1M context) --- diff --git a/Lib/test/test_ttk/test_extensions.py b/Lib/test/test_ttk/test_extensions.py index 669a3e184eb7..2765b226e271 100644 --- a/Lib/test/test_ttk/test_extensions.py +++ b/Lib/test/test_ttk/test_extensions.py @@ -281,6 +281,24 @@ class OptionMenuTest(AbstractTkTest, unittest.TestCase): optmenu.destroy() + def test_set_menu(self): + optmenu = ttk.OptionMenu(self.root, self.textvar, 'a', 'a', 'b', 'c') + menu = optmenu['menu'] + self.assertEqual(menu.index('end'), 2) + + # set_menu rebuilds the menu with new values and an optional default. + optmenu.set_menu('y', 'x', 'y', 'z') + self.assertEqual(self.textvar.get(), 'y') + self.assertEqual([menu.entrycget(i, 'label') for i in range(3)], + ['x', 'y', 'z']) + + # Without a default the variable is left unchanged. + optmenu.set_menu(None, 'p', 'q') + self.assertEqual(self.textvar.get(), 'y') + self.assertEqual([menu.entrycget(i, 'label') for i in range(2)], + ['p', 'q']) + optmenu.destroy() + def test_unique_radiobuttons(self): # check that radiobuttons are unique across instances (bpo25684) items = ('a', 'b', 'c') diff --git a/Lib/test/test_ttk/test_style.py b/Lib/test/test_ttk/test_style.py index fdbaae1b644e..f85f76eb4992 100644 --- a/Lib/test/test_ttk/test_style.py +++ b/Lib/test/test_ttk/test_style.py @@ -124,6 +124,22 @@ class StyleTest(AbstractTkTest, unittest.TestCase): self.style.theme_use(curr_theme) + def test_theme_settings(self): + style = self.style + theme = style.theme_use() + style.theme_settings(theme, { + 'Test.TLabel': { + 'configure': {'foreground': 'red', 'background': 'blue'}, + 'map': {'foreground': [('active', 'green')]}, + }, + }) + self.assertEqual(style.lookup('Test.TLabel', 'foreground'), 'red') + self.assertEqual(style.lookup('Test.TLabel', 'background'), 'blue') + self.assertEqual(style.map('Test.TLabel', 'foreground'), + [('active', 'green')]) + self.assertRaises(tkinter.TclError, style.theme_settings, + 'nonexistingname', {}) + def test_configure_custom_copy(self): style = self.style diff --git a/Lib/test/test_ttk/test_widgets.py b/Lib/test/test_ttk/test_widgets.py index 8cce9aed9d51..adcd736cd40b 100644 --- a/Lib/test/test_ttk/test_widgets.py +++ b/Lib/test/test_ttk/test_widgets.py @@ -973,6 +973,27 @@ class ProgressbarTest(AbstractWidgetTest, unittest.TestCase): test_configure_wraplength = requires_tk(8, 7)(StandardOptionsTests.test_configure_wraplength) + def test_step(self): + widget = self.create(maximum=100, mode='determinate') + self.assertEqual(float(widget['value']), 0.0) + widget.step() # The default increment is 1.0. + self.assertEqual(float(widget['value']), 1.0) + widget.step(5) + self.assertEqual(float(widget['value']), 6.0) + widget.step(-2) + self.assertEqual(float(widget['value']), 4.0) + + def test_start_stop(self): + widget = self.create(maximum=100, mode='determinate') + widget.pack() + widget.start() # Schedule autoincrement; no exception. + widget.update() + widget.stop() # Cancel it. + # After stopping, the value no longer changes. + value = float(widget['value']) + widget.update() + self.assertEqual(float(widget['value']), value) + @unittest.skipIf(sys.platform == 'darwin', 'ttk.Scrollbar is special on MacOSX') @@ -1639,6 +1660,50 @@ class TreeviewTest(AbstractWidgetTest, unittest.TestCase): # in the tcl interpreter since tk requires an item. self.assertRaises(tkinter.TclError, self.tv.exists, None) + def test_parent(self): + a = self.tv.insert('', 'end') + b = self.tv.insert(a, 'end') + self.assertEqual(self.tv.parent(b), a) + self.assertEqual(self.tv.parent(a), '') + self.assertRaises(tkinter.TclError, self.tv.parent, 'nonexistent') + + def test_next_prev(self): + a = self.tv.insert('', 'end') + b = self.tv.insert('', 'end') + c = self.tv.insert('', 'end') + self.assertEqual(self.tv.next(a), b) + self.assertEqual(self.tv.next(b), c) + self.assertEqual(self.tv.next(c), '') + self.assertEqual(self.tv.prev(c), b) + self.assertEqual(self.tv.prev(b), a) + self.assertEqual(self.tv.prev(a), '') + self.assertRaises(tkinter.TclError, self.tv.next, 'nonexistent') + self.assertRaises(tkinter.TclError, self.tv.prev, 'nonexistent') + + def test_see(self): + a = self.tv.insert('', 'end') + b = self.tv.insert(a, 'end') + # see() opens all of the item's ancestors. + self.assertFalse(self.tv.tk.getboolean(self.tv.item(a, 'open'))) + self.tv.see(b) + self.assertTrue(self.tv.tk.getboolean(self.tv.item(a, 'open'))) + self.assertRaises(tkinter.TclError, self.tv.see, 'nonexistent') + + def test_identify_element(self): + self.tv.pack() + self.tv.wait_visibility() + parent = self.tv.insert('', 'end', text='parent') + self.tv.insert(parent, 'end', text='child') + self.tv.update() + x, y, w, h = self.tv.bbox(parent) + # The Treeitem.indicator element is packed at the left of the row in + # the Item layout on every platform and theme. + element = self.tv.identify_element(x + 8, y + h // 2) + self.assertRegex(element, r'.*indicator\z') + # The empty string is returned outside the widget. + self.assertEqual(self.tv.identify_element(-1, -1), '') + self.assertRaises(tkinter.TclError, self.tv.identify_element, None, 5) + def test_focus(self): # nothing is focused right now self.assertEqual(self.tv.focus(), '')