https://github.com/python/cpython/commit/a2b6e57a54082895b23dd4573cc22b466e67c744
commit: a2b6e57a54082895b23dd4573cc22b466e67c744
branch: 3.13
author: Miss Islington (bot) <[email protected]>
committer: terryjreedy <[email protected]>
date: 2026-07-22T01:42:44Z
summary:

[3.13] gh-153741: Harden IDLE GUI tests for newer Tk and display scaling 
(GH-153742) (#154406)

gh-153741: Harden IDLE GUI tests for newer Tk and display scaling (GH-153742)

In test_sidebar.test_mousewheel, only use the X11 <Button-4>/<Button-5> events 
when testing on
x11 Tk before 8.7.  Otherwise, use <Mousewheel> as on other systems.

In test_configdialog, assert the 'disabled' state flag alone instead of
the exact ttk state tuple (which transient pointer states break), and
restore the tests' method masks with addCleanup().
(cherry picked from commit a2581ebc7a93ec573f64d3a5e29cbd9053de7a1f)

Co-authored-by: Serhiy Storchaka <[email protected]>
Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]>

files:
M Lib/idlelib/idle_test/test_configdialog.py
M Lib/idlelib/idle_test/test_sidebar.py

diff --git a/Lib/idlelib/idle_test/test_configdialog.py 
b/Lib/idlelib/idle_test/test_configdialog.py
index c2870442d5a6d35..8e435da0fa8898c 100644
--- a/Lib/idlelib/idle_test/test_configdialog.py
+++ b/Lib/idlelib/idle_test/test_configdialog.py
@@ -245,6 +245,7 @@ def test_font_set(self):
 
     def test_set_samples(self):
         d = self.page
+        self.addCleanup(setattr, d, 'set_samples', Func())  # Re-mask for 
other tests.
         del d.set_samples  # Unmask method for test
         orig_samples = d.font_sample, d.highlight_sample
         d.font_sample, d.highlight_sample = {}, {}
@@ -258,7 +259,6 @@ def test_set_samples(self):
         self.assertTrue(d.font_sample == d.highlight_sample == expected)
 
         d.font_sample, d.highlight_sample = orig_samples
-        d.set_samples = Func()  # Re-mask for other tests.
 
 
 class HighPageTest(unittest.TestCase):
@@ -308,7 +308,7 @@ def test_load_theme_cfg(self):
         # builtinlist sets variable builtin_name to the CurrentTheme default.
         eq(d.builtin_name.get(), 'IDLE Classic')
         eq(d.custom_name.get(), '- no custom themes -')
-        eq(d.custom_theme_on.state(), ('disabled',))
+        self.assertIn('disabled', d.custom_theme_on.state())
         eq(d.set_theme_type.called, 1)
         eq(d.paint_theme_sample.called, 1)
         eq(d.set_highlight_target.called, 1)
@@ -496,6 +496,7 @@ def test_highlight_sample_b1_motion(self):
     def test_set_theme_type(self):
         eq = self.assertEqual
         d = self.page
+        self.addCleanup(setattr, d, 'set_theme_type', Func())  # Re-mask 
method.
         del d.set_theme_type
 
         # Builtin theme selected.
@@ -503,16 +504,15 @@ def test_set_theme_type(self):
         d.set_theme_type()
         eq(d.builtinlist['state'], NORMAL)
         eq(d.customlist['state'], DISABLED)
-        eq(d.button_delete_custom.state(), ('disabled',))
+        self.assertIn('disabled', d.button_delete_custom.state())
 
         # Custom theme selected.
         d.theme_source.set(False)
         d.set_theme_type()
         eq(d.builtinlist['state'], DISABLED)
-        eq(d.custom_theme_on.state(), ('selected',))
+        self.assertNotIn('disabled', d.custom_theme_on.state())
         eq(d.customlist['state'], NORMAL)
-        eq(d.button_delete_custom.state(), ())
-        d.set_theme_type = Func()
+        self.assertNotIn('disabled', d.button_delete_custom.state())
 
     def test_get_color(self):
         eq = self.assertEqual
@@ -638,24 +638,23 @@ def test_create_new_and_save_new(self):
     def test_set_highlight_target(self):
         eq = self.assertEqual
         d = self.page
+        self.addCleanup(setattr, d, 'set_highlight_target', Func())  # Re-mask 
method.
         del d.set_highlight_target
 
         # Target is cursor.
         d.highlight_target.set('Cursor')
-        eq(d.fg_on.state(), ('disabled', 'selected'))
-        eq(d.bg_on.state(), ('disabled',))
+        self.assertIn('disabled', d.fg_on.state())
+        self.assertIn('disabled', d.bg_on.state())
         self.assertTrue(d.fg_bg_toggle)
         eq(d.set_color_sample.called, 1)
 
         # Target is not cursor.
         d.highlight_target.set('Comment')
-        eq(d.fg_on.state(), ('selected',))
-        eq(d.bg_on.state(), ())
+        self.assertNotIn('disabled', d.fg_on.state())
+        self.assertNotIn('disabled', d.bg_on.state())
         self.assertTrue(d.fg_bg_toggle)
         eq(d.set_color_sample.called, 2)
 
-        d.set_highlight_target = Func()
-
     def test_set_color_sample_binding(self):
         d = self.page
         scs = d.set_color_sample
@@ -668,6 +667,7 @@ def test_set_color_sample_binding(self):
 
     def test_set_color_sample(self):
         d = self.page
+        self.addCleanup(setattr, d, 'set_color_sample', Func())  # Re-mask 
method.
         del d.set_color_sample
         d.highlight_target.set('Selected Text')
         d.fg_bg_toggle.set(True)
@@ -675,7 +675,6 @@ def test_set_color_sample(self):
         self.assertEqual(
                 d.style.lookup(d.frame_color_set['style'], 'background'),
                 d.highlight_sample.tag_cget('hilite', 'foreground'))
-        d.set_color_sample = Func()
 
     def test_paint_theme_sample(self):
         eq = self.assertEqual
@@ -746,7 +745,7 @@ def test_delete_custom(self):
         eq(yesno.called, 2)
         self.assertNotIn(theme_name, highpage)
         eq(idleConf.GetSectionList('user', 'highlight'), [theme_name2])
-        eq(d.custom_theme_on.state(), ())
+        self.assertNotIn('disabled', d.custom_theme_on.state())
         eq(d.custom_name.get(), theme_name2)
         eq(dialog.deactivate_current_config.called, 1)
         eq(dialog.activate_config_changes.called, 1)
@@ -759,7 +758,7 @@ def test_delete_custom(self):
         eq(yesno.called, 3)
         self.assertNotIn(theme_name, highpage)
         eq(idleConf.GetSectionList('user', 'highlight'), [])
-        eq(d.custom_theme_on.state(), ('disabled',))
+        self.assertIn('disabled', d.custom_theme_on.state())
         eq(d.custom_name.get(), '- no custom themes -')
         eq(dialog.deactivate_current_config.called, 2)
         eq(dialog.activate_config_changes.called, 2)
@@ -810,7 +809,7 @@ def test_load_key_cfg(self):
         # builtinlist sets variable builtin_name to the CurrentKeys default.
         eq(d.builtin_name.get(), 'IDLE Classic OSX')
         eq(d.custom_name.get(), '- no custom keys -')
-        eq(d.custom_keyset_on.state(), ('disabled',))
+        self.assertIn('disabled', d.custom_keyset_on.state())
         eq(d.set_keys_type.called, 1)
         eq(d.load_keys_list.called, 1)
         eq(d.load_keys_list.args, ('IDLE Classic OSX', ))
@@ -931,6 +930,7 @@ def test_keybinding(self):
     def test_set_keys_type(self):
         eq = self.assertEqual
         d = self.page
+        self.addCleanup(setattr, d, 'set_keys_type', Func())  # Re-mask method.
         del d.set_keys_type
 
         # Builtin keyset selected.
@@ -938,16 +938,15 @@ def test_set_keys_type(self):
         d.set_keys_type()
         eq(d.builtinlist['state'], NORMAL)
         eq(d.customlist['state'], DISABLED)
-        eq(d.button_delete_custom_keys.state(), ('disabled',))
+        self.assertIn('disabled', d.button_delete_custom_keys.state())
 
         # Custom keyset selected.
         d.keyset_source.set(False)
         d.set_keys_type()
         eq(d.builtinlist['state'], DISABLED)
-        eq(d.custom_keyset_on.state(), ('selected',))
+        self.assertNotIn('disabled', d.custom_keyset_on.state())
         eq(d.customlist['state'], NORMAL)
-        eq(d.button_delete_custom_keys.state(), ())
-        d.set_keys_type = Func()
+        self.assertNotIn('disabled', d.button_delete_custom_keys.state())
 
     def test_get_new_keys(self):
         eq = self.assertEqual
@@ -1044,7 +1043,7 @@ def test_on_bindingslist_select(self):
         b.event_generate('<Button-1>', x=x, y=y)
         b.event_generate('<ButtonRelease-1>', x=x, y=y)
         self.assertEqual(b.get('anchor'), 'find')
-        self.assertEqual(d.button_new_keys.state(), ())
+        self.assertNotIn('disabled', d.button_new_keys.state())
 
     def test_create_new_key_set_and_save_new_key_set(self):
         eq = self.assertEqual
@@ -1081,6 +1080,7 @@ def test_load_keys_list(self):
         eq = self.assertEqual
         d = self.page
         gks = idleConf.GetKeySet = Func()
+        self.addCleanup(setattr, d, 'load_keys_list', Func())  # Re-mask 
method.
         del d.load_keys_list
         b = d.bindingslist
 
@@ -1116,7 +1116,6 @@ def test_load_keys_list(self):
         eq(b.get(0, 'end'), expected)
         eq(b.get('anchor'), 'spam - <Shift-Key-a>')
         eq(b.curselection(), (2, ))
-        d.load_keys_list = Func()
 
         del idleConf.GetKeySet
 
@@ -1157,7 +1156,7 @@ def test_delete_custom_keys(self):
         eq(yesno.called, 2)
         self.assertNotIn(keyset_name, keyspage)
         eq(idleConf.GetSectionList('user', 'keys'), [keyset_name2])
-        eq(d.custom_keyset_on.state(), ())
+        self.assertNotIn('disabled', d.custom_keyset_on.state())
         eq(d.custom_name.get(), keyset_name2)
         eq(dialog.deactivate_current_config.called, 1)
         eq(dialog.activate_config_changes.called, 1)
@@ -1170,7 +1169,7 @@ def test_delete_custom_keys(self):
         eq(yesno.called, 3)
         self.assertNotIn(keyset_name, keyspage)
         eq(idleConf.GetSectionList('user', 'keys'), [])
-        eq(d.custom_keyset_on.state(), ('disabled',))
+        self.assertIn('disabled', d.custom_keyset_on.state())
         eq(d.custom_name.get(), '- no custom keys -')
         eq(dialog.deactivate_current_config.called, 2)
         eq(dialog.activate_config_changes.called, 2)
@@ -1366,25 +1365,25 @@ def test_set_add_delete_state(self):
         # Call with 0 items, 1 unselected item, 1 selected item.
         eq = self.assertEqual
         fr = self.frame
+        self.addCleanup(setattr, fr, 'set_add_delete_state', Func())  # 
Re-mask method.
         del fr.set_add_delete_state  # Unmask method.
         sad = fr.set_add_delete_state
         h = fr.helplist
 
         h.delete(0, 'end')
         sad()
-        eq(fr.button_helplist_edit.state(), ('disabled',))
-        eq(fr.button_helplist_remove.state(), ('disabled',))
+        self.assertIn('disabled', fr.button_helplist_edit.state())
+        self.assertIn('disabled', fr.button_helplist_remove.state())
 
         h.insert(0, 'source')
         sad()
-        eq(fr.button_helplist_edit.state(), ('disabled',))
-        eq(fr.button_helplist_remove.state(), ('disabled',))
+        self.assertIn('disabled', fr.button_helplist_edit.state())
+        self.assertIn('disabled', fr.button_helplist_remove.state())
 
         h.selection_set(0)
         sad()
-        eq(fr.button_helplist_edit.state(), ())
-        eq(fr.button_helplist_remove.state(), ())
-        fr.set_add_delete_state = Func()  # Mask method.
+        self.assertNotIn('disabled', fr.button_helplist_edit.state())
+        self.assertNotIn('disabled', fr.button_helplist_remove.state())
 
     def test_helplist_item_add(self):
         # Call without and twice with HelpSource result.
@@ -1462,6 +1461,7 @@ def test_helplist_item_remove(self):
 
     def test_update_help_changes(self):
         fr = self.frame
+        self.addCleanup(setattr, fr, 'update_help_changes', Func())  # Re-mask 
method.
         del fr.update_help_changes
         fr.user_helplist.clear()
         fr.user_helplist.append(('name1', 'file1'))
@@ -1470,7 +1470,6 @@ def test_update_help_changes(self):
         fr.update_help_changes()
         self.assertEqual(mainpage['HelpFiles'],
                          {'1': 'name1;file1', '2': 'name2;file2'})
-        fr.update_help_changes = Func()
 
 
 class VarTraceTest(unittest.TestCase):
diff --git a/Lib/idlelib/idle_test/test_sidebar.py 
b/Lib/idlelib/idle_test/test_sidebar.py
index 4b3429a270d7b87..25723b0a6abdf76 100644
--- a/Lib/idlelib/idle_test/test_sidebar.py
+++ b/Lib/idlelib/idle_test/test_sidebar.py
@@ -689,11 +689,15 @@ def test_mousewheel(self):
         last_lineno = get_end_linenumber(text)
         self.assertIsNotNone(text.dlineinfo(text.index(f'{last_lineno}.0')))
 
-        # Delta for <MouseWheel>, whose meaning is platform-dependent.
+        # Simulate a mouse wheel notch.  Tk 8.7 replaced the X11
+        # <Button-4>/<Button-5> wheel events with <MouseWheel> (whose delta is
+        # platform-dependent); older Tk on X11 still uses the button events.
+        x11_buttons = (sidebar.canvas._windowingsystem == 'x11'
+                       and tk.TkVersion < 8.7)
         delta = 1 if sidebar.canvas._windowingsystem == 'aqua' else 120
 
         # Scroll up.
-        if sidebar.canvas._windowingsystem == 'x11':
+        if x11_buttons:
             sidebar.canvas.event_generate('<Button-4>', x=0, y=0)
         else:
             sidebar.canvas.event_generate('<MouseWheel>', x=0, y=0, 
delta=delta)
@@ -701,7 +705,7 @@ def test_mousewheel(self):
         self.assertIsNone(text.dlineinfo(text.index(f'{last_lineno}.0')))
 
         # Scroll back down.
-        if sidebar.canvas._windowingsystem == 'x11':
+        if x11_buttons:
             sidebar.canvas.event_generate('<Button-5>', x=0, y=0)
         else:
             sidebar.canvas.event_generate('<MouseWheel>', x=0, y=0, 
delta=-delta)

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]

Reply via email to