Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Lib/idlelib/idle_test/test_configdialog.py
Original file line number Diff line number Diff line change
Expand Up @@ -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('<Key-Down>')
fontlist.event_generate('<KeyRelease-Down>')

Expand All @@ -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('<Key-Up>')
fontlist.event_generate('<KeyRelease-Up>')

Expand Down
16 changes: 16 additions & 0 deletions Lib/test/test_tkinter/support.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -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():
Expand All @@ -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)
Expand Down
4 changes: 2 additions & 2 deletions Lib/test/test_tkinter/test_filedialog.py
Original file line number Diff line number Diff line change
Expand Up @@ -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')

Expand Down Expand Up @@ -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.

Expand Down
15 changes: 11 additions & 4 deletions Lib/test/test_tkinter/test_misc.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down
16 changes: 10 additions & 6 deletions Lib/test/test_tkinter/test_simpledialog.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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.

Expand Down Expand Up @@ -50,8 +55,8 @@ def test_return_activates_default(self):
# <Return> 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('<Return>')
d.root.update()
self.assertEqual(d.num, 0)
Expand All @@ -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('<Return>')
d.root.update()
self.assertTrue(bells) # rang the bell
Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -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 <Return>/<Escape> bindings and the validation.
Expand Down
6 changes: 5 additions & 1 deletion Lib/test/test_ttk/test_widgets.py
Original file line number Diff line number Diff line change
Expand Up @@ -2030,19 +2030,23 @@ def test_virtual_events(self):
lambda e: selects.append(self.tv.selection()))
self.tv.bind('<<TreeviewOpen>>', lambda e: opens.append(self.tv.focus()))
self.tv.bind('<<TreeviewClose>>', 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('<Right>') # Open the focused parent.
self.tv.update()
self.assertEqual(opens, [parent])

self.tv.focus_force()
self.tv.event_generate('<Left>') # Close it again.
self.tv.update()
self.assertEqual(closes, [parent])

self.tv.focus_force()
self.tv.event_generate('<Down>') # Move the selection.
self.tv.update()
self.assertEqual(self.tv.selection(), (item2,))
Expand Down
Loading