From: Serhiy Storchaka Date: Fri, 19 Jun 2026 14:38:32 +0000 (+0300) Subject: gh-151678: Add tests for tkinter Misc, Wm and geometry manager methods (GH-151732) X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=23793ac211371415eaf9491fef031f121969dfb3;p=thirdparty%2FPython%2Fcpython.git gh-151678: Add tests for tkinter Misc, Wm and geometry manager methods (GH-151732) Cover previously-untested methods of the Misc, Wm, Pack, Place and Grid classes: * a new WinfoTest class for the winfo_* query methods (class, name, parent, children, toplevel, visual and screen information, atoms, pointer and screen coordinates, fpixels, containing, interps, viewable), including the pre-existing winfo_rgb and winfo_pathname; * in MiscTest: getint, getdouble, getvar, register, deletecommand, option_add/get/clear, nametowidget, the focus_*, grab_* and selection_own* methods, event_add/delete/info, and bell; * in WmTest: title, geometry, minsize/maxsize, resizable, aspect, grid, positionfrom/sizefrom, focusmodel, iconname, client/command, overrideredirect, state (with withdraw and deiconify), frame, group, protocol and transient; * the short-named aliases of the grid_*, pack_* and place_* geometry manager methods. Co-Authored-By: Claude Opus 4.8 (1M context) --- diff --git a/Lib/test/test_tkinter/test_geometry_managers.py b/Lib/test/test_tkinter/test_geometry_managers.py index 3dcdadd1aacf..7fb0abff0049 100644 --- a/Lib/test/test_tkinter/test_geometry_managers.py +++ b/Lib/test/test_tkinter/test_geometry_managers.py @@ -289,6 +289,25 @@ class PackTest(AbstractWidgetTest, unittest.TestCase): b.pack_configure() self.assertEqual(pack.pack_slaves(), [a, b]) + def test_pack_short_aliases(self): + # slaves, content and propagate are aliases of the pack_* methods + # (Misc precedes Pack, Place and Grid in the method resolution order). + pack, a, b, c, d = self.create2() + self.assertEqual(pack.slaves, pack.pack_slaves) + self.assertEqual(pack.content, pack.pack_content) + self.assertEqual(pack.propagate, pack.pack_propagate) + + self.assertEqual(pack.slaves(), []) + a.pack_configure() + self.assertEqual(pack.slaves(), [a]) + self.assertEqual(pack.content(), [a]) + + pack.configure(width=300, height=200) + pack.propagate(False) + self.root.update() + self.assertEqual(pack.winfo_reqwidth(), 300) + self.assertEqual(pack.winfo_reqheight(), 200) + class PlaceTest(AbstractWidgetTest, unittest.TestCase): @@ -503,6 +522,18 @@ class PlaceTest(AbstractWidgetTest, unittest.TestCase): with self.assertRaises(TypeError): foo.place_slaves(0) + def test_place_method_aliases(self): + # The Place manager defines configure, info, forget, slaves and + # content as aliases of its place_* methods. On a real widget the + # short names are provided by Misc and Pack (earlier in the method + # resolution order), so the aliases are checked on the class itself. + self.assertIs(tkinter.Place.configure, tkinter.Place.place_configure) + self.assertIs(tkinter.Place.config, tkinter.Place.place_configure) + self.assertIs(tkinter.Place.info, tkinter.Place.place_info) + self.assertIs(tkinter.Place.forget, tkinter.Place.place_forget) + self.assertIs(tkinter.Place.slaves, tkinter.Misc.place_slaves) + self.assertIs(tkinter.Place.content, tkinter.Misc.place_content) + class GridTest(AbstractWidgetTest, unittest.TestCase): @@ -938,6 +969,30 @@ class GridTest(AbstractWidgetTest, unittest.TestCase): self.assertEqual(self.root.grid_slaves(column=1), [d, c, a]) self.assertEqual(self.root.grid_slaves(row=1, column=1), [d, c]) + def test_grid_short_aliases(self): + # columnconfigure, rowconfigure, size, anchor and bbox are aliases of + # the corresponding grid_* methods (Misc precedes Pack, Place and Grid + # in the method resolution order). + root = self.root + self.assertEqual(root.columnconfigure, root.grid_columnconfigure) + self.assertEqual(root.rowconfigure, root.grid_rowconfigure) + self.assertEqual(root.size, root.grid_size) + self.assertEqual(root.anchor, root.grid_anchor) + self.assertEqual(root.bbox, root.grid_bbox) + + self.assertEqual(root.size(), (0, 0)) + b = tkinter.Button(root) + b.grid_configure(column=2, row=3) + self.assertEqual(root.size(), (3, 4)) + + root.columnconfigure(0, weight=2) + self.assertEqual(root.grid_columnconfigure(0, 'weight'), 2) + root.rowconfigure(0, weight=3) + self.assertEqual(root.grid_rowconfigure(0, 'weight'), 3) + + root.anchor('se') + self.assertEqual(root.tk.call('grid', 'anchor', root), 'se') + if __name__ == '__main__': unittest.main() diff --git a/Lib/test/test_tkinter/test_misc.py b/Lib/test/test_tkinter/test_misc.py index 2dc3bdfcec40..1b32689bb596 100644 --- a/Lib/test/test_tkinter/test_misc.py +++ b/Lib/test/test_tkinter/test_misc.py @@ -321,44 +321,101 @@ class MiscTest(AbstractTkTest, unittest.TestCase): with self.assertRaises(tkinter.TclError): root.clipboard_get() - def test_winfo_rgb(self): - - def assertApprox(col1, col2): - # A small amount of flexibility is required (bpo-45496) - # 33 is ~0.05% of 65535, which is a reasonable margin - for col1_channel, col2_channel in zip(col1, col2): - self.assertAlmostEqual(col1_channel, col2_channel, delta=33) - - root = self.root - rgb = root.winfo_rgb - - # Color name. - self.assertEqual(rgb('red'), (65535, 0, 0)) - self.assertEqual(rgb('dark slate blue'), (18504, 15677, 35723)) - # #RGB - extends each 4-bit hex value to be 16-bit. - self.assertEqual(rgb('#F0F'), (0xFFFF, 0x0000, 0xFFFF)) - # #RRGGBB - extends each 8-bit hex value to be 16-bit. - assertApprox(rgb('#4a3c8c'), (0x4a4a, 0x3c3c, 0x8c8c)) - # #RRRRGGGGBBBB - assertApprox(rgb('#dede14143939'), (0xdede, 0x1414, 0x3939)) - # Invalid string. - with self.assertRaises(tkinter.TclError): - rgb('#123456789a') - # RGB triplet is invalid input. - with self.assertRaises(tkinter.TclError): - rgb((111, 78, 55)) + def test_getint(self): + self.assertEqual(self.root.getint('42'), 42) + self.assertEqual(self.root.getint(42), 42) + self.assertEqual(self.root.getint('-5'), -5) + self.assertRaises(ValueError, self.root.getint, 'spam') + + def test_getdouble(self): + self.assertEqual(self.root.getdouble('3.5'), 3.5) + self.assertEqual(self.root.getdouble(3), 3.0) + self.assertRaises(ValueError, self.root.getdouble, 'spam') + + def test_getvar(self): + self.root.setvar('test_var', 'hello') + self.assertEqual(self.root.getvar('test_var'), 'hello') + + def test_register(self): + result = [] + def callback(): + result.append(1) + return 'spam' + name = self.root.register(callback) + self.assertIsInstance(name, str) + self.assertEqual(self.root.tk.call(name), 'spam') + self.assertEqual(result, [1]) + self.root.deletecommand(name) + self.assertRaises(TclError, self.root.tk.call, name) + + def test_option(self): + self.addCleanup(self.root.option_clear) + self.root.option_add('*Button.background', 'red') + b = tkinter.Button(self.root) + self.assertEqual(b.option_get('background', 'Background'), 'red') + self.assertEqual(b.option_get('foreground', 'Foreground'), '') + self.root.option_clear() + self.assertEqual(b.option_get('background', 'Background'), '') + + def test_nametowidget(self): + b = tkinter.Button(self.root, name='btn') + self.assertIs(self.root.nametowidget('btn'), b) + self.assertIs(self.root.nametowidget(str(b)), b) + self.assertRaises(KeyError, self.root.nametowidget, '.nonexistent') + + def test_focus_methods(self): + f = tkinter.Frame(self.root, width=150, height=100) + f.pack() + self.root.wait_visibility() # needed on Windows + self.root.update_idletasks() + f.focus_force() + self.root.update() + self.assertIs(self.root.focus_get(), f) + self.assertIs(self.root.focus_displayof(), f) + self.assertIs(f.focus_lastfor(), f) + b = tkinter.Button(f) + b.pack() + self.root.update() + b.focus_set() + self.root.update() + self.assertIs(self.root.focus_get(), b) - def test_winfo_pathname(self): - t = tkinter.Toplevel(self.root) - w = tkinter.Button(t) - wid = w.winfo_id() - self.assertIsInstance(wid, int) - self.assertEqual(self.root.winfo_pathname(hex(wid)), str(w)) - self.assertEqual(self.root.winfo_pathname(hex(wid), displayof=None), str(w)) - self.assertEqual(self.root.winfo_pathname(hex(wid), displayof=t), str(w)) - self.assertEqual(self.root.winfo_pathname(wid), str(w)) - self.assertEqual(self.root.winfo_pathname(wid, displayof=None), str(w)) - self.assertEqual(self.root.winfo_pathname(wid, displayof=t), str(w)) + def test_grab(self): + f = tkinter.Frame(self.root) + f.pack() + self.root.wait_visibility() + self.assertIsNone(self.root.grab_current()) + self.assertIsNone(f.grab_status()) + f.grab_set() + self.assertEqual(f.grab_status(), 'local') + self.assertIs(self.root.grab_current(), f) + f.grab_release() + self.assertIsNone(f.grab_status()) + self.assertIsNone(self.root.grab_current()) + + def test_selection_own(self): + self.root.selection_own() + self.assertIs(self.root.selection_own_get(), self.root) + f = tkinter.Frame(self.root) + f.selection_own() + self.assertIs(self.root.selection_own_get(), f) + + def test_event_add_delete_info(self): + self.addCleanup(self.root.event_delete, '<>') + self.root.event_add('<>', '', '') + self.assertEqual(self.root.event_info('<>'), + ('', '')) + self.assertIn('<>', self.root.event_info()) + self.root.event_delete('<>', '') + self.assertEqual(self.root.event_info('<>'), + ('',)) + self.root.event_delete('<>') + self.assertEqual(self.root.event_info('<>'), ()) + self.assertRaises(TypeError, self.root.event_delete) + + def test_bell(self): + self.root.bell() # No exception. + self.root.bell(displayof=self.root) def test_event_repr_defaults(self): e = tkinter.Event() @@ -524,6 +581,150 @@ class MiscTest(AbstractTkTest, unittest.TestCase): widget in widget +class WinfoTest(AbstractTkTest, unittest.TestCase): + + def test_winfo_rgb(self): + + def assertApprox(col1, col2): + # A small amount of flexibility is required (bpo-45496) + # 33 is ~0.05% of 65535, which is a reasonable margin + for col1_channel, col2_channel in zip(col1, col2): + self.assertAlmostEqual(col1_channel, col2_channel, delta=33) + + root = self.root + rgb = root.winfo_rgb + + # Color name. + self.assertEqual(rgb('red'), (65535, 0, 0)) + self.assertEqual(rgb('dark slate blue'), (18504, 15677, 35723)) + # #RGB - extends each 4-bit hex value to be 16-bit. + self.assertEqual(rgb('#F0F'), (0xFFFF, 0x0000, 0xFFFF)) + # #RRGGBB - extends each 8-bit hex value to be 16-bit. + assertApprox(rgb('#4a3c8c'), (0x4a4a, 0x3c3c, 0x8c8c)) + # #RRRRGGGGBBBB + assertApprox(rgb('#dede14143939'), (0xdede, 0x1414, 0x3939)) + # Invalid string. + with self.assertRaises(tkinter.TclError): + rgb('#123456789a') + # RGB triplet is invalid input. + with self.assertRaises(tkinter.TclError): + rgb((111, 78, 55)) + + def test_winfo_pathname(self): + t = tkinter.Toplevel(self.root) + w = tkinter.Button(t) + wid = w.winfo_id() + self.assertIsInstance(wid, int) + self.assertEqual(self.root.winfo_pathname(hex(wid)), str(w)) + self.assertEqual(self.root.winfo_pathname(hex(wid), displayof=None), str(w)) + self.assertEqual(self.root.winfo_pathname(hex(wid), displayof=t), str(w)) + self.assertEqual(self.root.winfo_pathname(wid), str(w)) + self.assertEqual(self.root.winfo_pathname(wid, displayof=None), str(w)) + self.assertEqual(self.root.winfo_pathname(wid, displayof=t), str(w)) + + def test_winfo_class_name_parent(self): + f = tkinter.Frame(self.root) + b = tkinter.Button(f) + self.assertEqual(f.winfo_class(), 'Frame') + self.assertEqual(b.winfo_class(), 'Button') + self.assertEqual(b.winfo_name(), str(b).rsplit('.', 1)[-1]) + self.assertEqual(b.winfo_parent(), str(f)) + self.assertEqual(f.winfo_parent(), str(self.root)) + self.assertIs(f.winfo_toplevel(), self.root) + t = tkinter.Toplevel(self.root) + self.assertIs(tkinter.Button(t).winfo_toplevel(), t) + self.assertEqual(self.root.nametowidget(b.winfo_parent()), f) + + def test_winfo_children(self): + self.assertEqual(self.root.winfo_children(), []) + f = tkinter.Frame(self.root) + b = tkinter.Button(f) + self.assertEqual(self.root.winfo_children(), [f]) + self.assertEqual(f.winfo_children(), [b]) + + def test_winfo_visual_info(self): + f = tkinter.Frame(self.root) + self.assertIsInstance(f.winfo_depth(), int) + self.assertIsInstance(f.winfo_cells(), int) + self.assertIsInstance(f.winfo_visual(), str) + self.assertIsInstance(f.winfo_visualid(), str) + self.assertIsInstance(f.winfo_colormapfull(), bool) + visuals = self.root.winfo_visualsavailable() + self.assertIsInstance(visuals, list) + for name, depth in visuals: + self.assertIsInstance(name, str) + self.assertIsInstance(depth, int) + + def test_winfo_viewable(self): + f = tkinter.Frame(self.root) + self.assertFalse(f.winfo_viewable()) + f.pack() + f.wait_visibility() + self.root.update() + self.assertTrue(f.winfo_viewable()) + + def test_winfo_atom(self): + atom = self.root.winfo_atom('PRIMARY') + self.assertIsInstance(atom, int) + self.assertEqual(self.root.winfo_atomname(atom), 'PRIMARY') + self.assertEqual( + self.root.winfo_atomname(atom, displayof=self.root), 'PRIMARY') + self.assertEqual( + self.root.winfo_atom('PRIMARY', displayof=self.root), atom) + self.assertRaisesRegex(TclError, 'no atom exists', + self.root.winfo_atomname, 10 ** 9) + + def test_winfo_pointer(self): + self.assertIsInstance(self.root.winfo_pointerx(), int) + self.assertIsInstance(self.root.winfo_pointery(), int) + xy = self.root.winfo_pointerxy() + self.assertIsInstance(xy, tuple) + self.assertEqual(len(xy), 2) + self.assertTrue(all(isinstance(v, int) for v in xy)) + + def test_winfo_containing(self): + self.root.update() + # No window contains a point far off the screen. + self.assertIsNone(self.root.winfo_containing(-10000, -10000)) + self.assertIsNone( + self.root.winfo_containing(-10000, -10000, displayof=self.root)) + + def test_winfo_fpixels(self): + self.assertIsInstance(self.root.winfo_fpixels('1i'), float) + self.assertAlmostEqual(self.root.winfo_fpixels('1i'), + self.root.winfo_fpixels('72p')) + # Tk < 9 reports 'bad screen distance "spam"', Tk 9 reports + # 'expected screen distance ... but got "spam"'. + self.assertRaisesRegex(TclError, + r'(bad|expected) screen distance.*"spam"', + self.root.winfo_fpixels, 'spam') + + def test_winfo_screen(self): + for name in ('winfo_screenwidth', 'winfo_screenheight', + 'winfo_screenmmwidth', 'winfo_screenmmheight', + 'winfo_screencells', 'winfo_screendepth'): + value = getattr(self.root, name)() + self.assertIsInstance(value, int) + self.assertGreater(value, 0) + self.assertIsInstance(self.root.winfo_screenvisual(), str) + self.assertIsInstance(self.root.winfo_screen(), str) + self.assertIsInstance(self.root.winfo_server(), str) + + def test_winfo_vroot(self): + for name in ('winfo_vrootwidth', 'winfo_vrootheight', + 'winfo_vrootx', 'winfo_vrooty'): + self.assertIsInstance(getattr(self.root, name)(), int) + + def test_winfo_interps(self): + interps = self.root.winfo_interps() + self.assertIsInstance(interps, tuple) + # The registry of interpreters is only populated where "send" is + # supported (i.e. X11), so do not require this interpreter's name. + if self.root._windowingsystem == 'x11': + self.assertIn(self.root.tk.call('tk', 'appname'), interps) + self.assertEqual(self.root.winfo_interps(displayof=self.root), interps) + + class WmTest(AbstractTkTest, unittest.TestCase): def test_wm_attribute(self): @@ -618,6 +819,120 @@ class WmTest(AbstractTkTest, unittest.TestCase): t.destroy() + def test_wm_title(self): + t = tkinter.Toplevel(self.root) + t.title('Hello') + self.assertEqual(t.title(), 'Hello') + self.assertEqual(t.wm_title(), 'Hello') + t.wm_title('Spam') + self.assertEqual(t.title(), 'Spam') + + def test_wm_geometry(self): + t = tkinter.Toplevel(self.root) + t.geometry('200x100+10+20') + t.update() + self.assertRegex(t.geometry(), r'^200x100\+-?\d+\+-?\d+$') + self.assertEqual(t.wm_geometry(), t.geometry()) + + def test_wm_minsize_maxsize(self): + t = tkinter.Toplevel(self.root) + # Use a width above the minimum enforced by some platforms (72 on Aqua). + t.minsize(150, 100) + self.assertEqual(t.minsize(), (150, 100)) + t.maxsize(500, 600) + self.assertEqual(t.maxsize(), (500, 600)) + + def test_wm_resizable(self): + t = tkinter.Toplevel(self.root) + t.resizable(False, True) + self.assertEqual(t.resizable(), (0, 1)) + self.assertRaisesRegex(TclError, 'expected boolean value', + t.resizable, 'spam', True) + + def test_wm_aspect(self): + t = tkinter.Toplevel(self.root) + self.assertEqual(t.aspect(), None) + t.aspect(1, 2, 3, 4) + self.assertEqual(t.aspect(), (1, 2, 3, 4)) + + def test_wm_grid(self): + t = tkinter.Toplevel(self.root) + t.wm_grid(10, 10, 5, 5) + self.assertEqual(t.wm_grid(), (10, 10, 5, 5)) + + def test_wm_positionfrom_sizefrom(self): + # These set X11 size hints and may be no-ops on other platforms. + t = tkinter.Toplevel(self.root) + t.positionfrom('user') + self.assertIn(t.positionfrom(), ('user', '')) + t.sizefrom('program') + self.assertIn(t.sizefrom(), ('program', '')) + + def test_wm_focusmodel(self): + t = tkinter.Toplevel(self.root) + self.assertEqual(t.focusmodel(), 'passive') + t.focusmodel('active') + self.assertEqual(t.focusmodel(), 'active') + self.assertRaises(TclError, t.focusmodel, 'spam') + + def test_wm_iconname(self): + # WM_ICON_NAME is an X11 property and may be a no-op elsewhere. + t = tkinter.Toplevel(self.root) + t.iconname('Icon') + self.assertIn(t.iconname(), ('Icon', '')) + + def test_wm_client_command(self): + t = tkinter.Toplevel(self.root) + t.client('myhost') + t.wm_command('myapp -x') + # WM_CLIENT_MACHINE and WM_COMMAND are X11 properties; elsewhere the + # setters may be no-ops and wm_command may return a split list. + if t._windowingsystem == 'x11': + self.assertEqual(t.client(), 'myhost') + self.assertEqual(t.wm_command(), 'myapp -x') + + def test_wm_overrideredirect(self): + t = tkinter.Toplevel(self.root) + self.assertFalse(t.overrideredirect()) + t.overrideredirect(True) + self.assertTrue(t.overrideredirect()) + + def test_wm_state(self): + t = tkinter.Toplevel(self.root) + t.update() + self.assertEqual(t.state(), 'normal') + t.withdraw() + self.assertEqual(t.state(), 'withdrawn') + t.deiconify() + t.update() + self.assertEqual(t.state(), 'normal') + self.assertRaises(TclError, t.state, 'spam') + + def test_wm_frame(self): + t = tkinter.Toplevel(self.root) + t.update() + self.assertIsInstance(t.frame(), str) + + def test_wm_group(self): + # The window group is an X11 concept and may be a no-op elsewhere. + t = tkinter.Toplevel(self.root) + t.group(self.root) + self.assertIn(t.group(), (str(self.root), '')) + + def test_wm_protocol(self): + t = tkinter.Toplevel(self.root) + self.assertIsInstance(t.protocol(), tuple) + t.protocol('WM_SAVE_YOURSELF', lambda: None) + self.assertIn('WM_SAVE_YOURSELF', t.protocol()) + # Querying a single protocol returns the bound command name. + self.assertTrue(t.protocol('WM_SAVE_YOURSELF')) + + def test_wm_transient(self): + t = tkinter.Toplevel(self.root) + self.assertEqual(t.transient(), '') + t.transient(self.root) + self.assertEqual(str(t.transient()), str(self.root)) + class EventTest(AbstractTkTest, unittest.TestCase):