diff --git a/Lib/idlelib/idle_test/test_configdialog.py b/Lib/idlelib/idle_test/test_configdialog.py index f3e1c785a92674c..24d47854dddd9e1 100644 --- a/Lib/idlelib/idle_test/test_configdialog.py +++ b/Lib/idlelib/idle_test/test_configdialog.py @@ -150,8 +150,8 @@ def test_fontlist_key(self): font = d.fontlist.get('active') # Test Down key. - fontlist.focus_force() fontlist.update() + fontlist.focus_force() fontlist.event_generate('') fontlist.event_generate('') @@ -160,8 +160,8 @@ def test_fontlist_key(self): self.assertIn(d.font_name.get(), down_font.lower()) # Test Up key. - fontlist.focus_force() fontlist.update() + fontlist.focus_force() fontlist.event_generate('') fontlist.event_generate('') diff --git a/Lib/test/test_tkinter/support.py b/Lib/test/test_tkinter/support.py index 31feee2b8a40ee6..8d4ad2345a16510 100644 --- a/Lib/test/test_tkinter/support.py +++ b/Lib/test/test_tkinter/support.py @@ -61,6 +61,16 @@ def require_mapped(self, widget, timeout=None): f'(timed out after {timeout:g}s)') +class AbstractDialogTest(AbstractTkTest): + # Tk delivers generated keyboard events to the focused window. Hide the + # root window, otherwise the window manager can take the focus back from + # the dialog (gh-154357). + + def setUp(self): + super().setUp() + self.root.withdraw() + + class AbstractDefaultRootTest: def setUp(self): @@ -112,6 +122,7 @@ def wait_until_mapped(widget, timeout=None, *, full_size=False): timeout = support.LOOPBACK_TIMEOUT deadline = time.monotonic() + timeout widget.update_idletasks() + reset = False while True: widget.update() # drain pending Map/Configure events if widget.winfo_ismapped(): @@ -123,6 +134,11 @@ def wait_until_mapped(widget, timeout=None, *, full_size=False): h_ok = widget.winfo_height() > 1 if w_ok and h_ok: return True + if full_size and not reset: + # Tk no longer resizes the toplevel to fit its content if + # the window manager has resized it. Undo this. + widget.winfo_toplevel().wm_geometry('') + reset = True if time.monotonic() >= deadline: return False time.sleep(0.01) diff --git a/Lib/test/test_tkinter/test_filedialog.py b/Lib/test/test_tkinter/test_filedialog.py index 054e719a0f883d9..e4e0f2b26c38bb5 100644 --- a/Lib/test/test_tkinter/test_filedialog.py +++ b/Lib/test/test_tkinter/test_filedialog.py @@ -4,7 +4,7 @@ from tkinter.commondialog import Dialog from test.support import requires, swap_attr from test.test_tkinter.support import setUpModule # noqa: F401 -from test.test_tkinter.support import AbstractTkTest +from test.test_tkinter.support import AbstractDialogTest, AbstractTkTest requires('gui') @@ -37,7 +37,7 @@ def test_directory(self): self.check(filedialog.Directory, 'tk_chooseDirectory') -class FileDialogTest(AbstractTkTest, unittest.TestCase): +class FileDialogTest(AbstractDialogTest, unittest.TestCase): # The pure-Python FileDialog runs its own modal loop in go(); its logic is # exercised here without entering the loop. diff --git a/Lib/test/test_tkinter/test_misc.py b/Lib/test/test_tkinter/test_misc.py index ee13cfd13a703b7..cc44449027f173d 100644 --- a/Lib/test/test_tkinter/test_misc.py +++ b/Lib/test/test_tkinter/test_misc.py @@ -451,15 +451,20 @@ def test_focus_methods(self): self.root.update_idletasks() f.focus_force() self.root.update() - self.assertIs(self.root.focus_get(), f) - self.assertIs(self.root.focus_displayof(), f) + # The window manager can take the focus away, and then focus_get() + # and focus_displayof() return None. + if self.root.focus_displayof() is not None: + 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) + if self.root.focus_displayof() is not None: + self.assertIs(self.root.focus_get(), b) + self.assertIs(f.focus_lastfor(), b) def test_focus_methods_unresolvable(self): # The focus may be on a widget that tkinter did not create and so @@ -1164,7 +1169,9 @@ def test_focus(self): f.focus_force() self.root.update() - self.assertEqual(len(events), 1, events) + # The window manager can take the focus away and give it back, + # which makes Tk generate additional focus events. + self.assertGreaterEqual(len(events), 1, events) e = events[0] self.assertIs(e.type, tkinter.EventType.FocusIn) self.assertIs(e.widget, f) diff --git a/Lib/test/test_tkinter/test_simpledialog.py b/Lib/test/test_tkinter/test_simpledialog.py index 64edbb645bc80fa..1cb03074d89a76f 100644 --- a/Lib/test/test_tkinter/test_simpledialog.py +++ b/Lib/test/test_tkinter/test_simpledialog.py @@ -3,7 +3,8 @@ from tkinter import messagebox from test.support import requires, swap_attr from test.test_tkinter.support import setUpModule # noqa: F401 -from test.test_tkinter.support import AbstractDefaultRootTest, AbstractTkTest +from test.test_tkinter.support import (AbstractDefaultRootTest, + AbstractDialogTest, AbstractTkTest) from tkinter.simpledialog import (Dialog, SimpleDialog, askinteger, askfloat, askstring, _QueryInteger, _QueryFloat, _QueryString) @@ -12,6 +13,10 @@ class SimpleDialogTest(AbstractTkTest, unittest.TestCase): + # The root window is not hidden here (cf. AbstractDialogTest): SimpleDialog + # makes its window transient for the master unconditionally, and a + # transient of a hidden window is never mapped on Windows, so that go() + # would block in wait_visibility(). # SimpleDialog's modal loop is in go(); its bindings are exercised here by # generating events on the constructed dialog, without entering the loop. @@ -50,8 +55,8 @@ def test_return_activates_default(self): # invokes the default button. d = self.create() # default 0 self.require_mapped(d.root) - d.root.focus_force() d.root.update() + d.root.focus_force() d.root.event_generate('') d.root.update() self.assertEqual(d.num, 0) @@ -61,10 +66,9 @@ def test_return_no_default(self): # open instead of activating a button. d = self.create(default=None) self.require_mapped(d.root) - d.root.focus_force() - d.root.update() bells = [] with swap_attr(d.root, 'bell', lambda *a, **k: bells.append(True)): + d.root.focus_force() d.root.event_generate('') d.root.update() self.assertTrue(bells) # rang the bell @@ -98,7 +102,7 @@ def test_go(self): self.assertEqual(d.go(), 0) -class DialogTest(AbstractTkTest, unittest.TestCase): +class DialogTest(AbstractDialogTest, unittest.TestCase): # Dialog is a base class for custom dialogs; exercise it via _QueryInteger. def open(self, **kw): @@ -167,7 +171,7 @@ def mock_wait_window(w): self.assertRaises(RuntimeError, askinteger, "Go To Line", "Line number") -class QueryDialogTest(AbstractTkTest, unittest.TestCase): +class QueryDialogTest(AbstractDialogTest, unittest.TestCase): # The query dialogs are modal: their __init__ blocks in wait_window(). # Mock that out so the dialog stays alive and can be driven with generated # events, exercising the / bindings and the validation. diff --git a/Lib/test/test_ttk/test_widgets.py b/Lib/test/test_ttk/test_widgets.py index 53bcd5ed2aab1b0..9fd7a7c9f06ba3e 100644 --- a/Lib/test/test_ttk/test_widgets.py +++ b/Lib/test/test_ttk/test_widgets.py @@ -2030,19 +2030,23 @@ def test_virtual_events(self): lambda e: selects.append(self.tv.selection())) self.tv.bind('<>', lambda e: opens.append(self.tv.focus())) self.tv.bind('<>', lambda e: closes.append(self.tv.focus())) - self.tv.focus_force() self.tv.focus(parent) self.tv.selection_set(parent) self.tv.update() + # Force the focus right before generating the event: the window + # manager can take it back. + self.tv.focus_force() self.tv.event_generate('') # Open the focused parent. self.tv.update() self.assertEqual(opens, [parent]) + self.tv.focus_force() self.tv.event_generate('') # Close it again. self.tv.update() self.assertEqual(closes, [parent]) + self.tv.focus_force() self.tv.event_generate('') # Move the selection. self.tv.update() self.assertEqual(self.tv.selection(), (item2,))