Skip to content

Commit 706238e

Browse files
gh-151678: Add interactive tests for tkinter.simpledialog (GH-151794)
Drive the modal query dialogs with generated events to exercise the <Return> and <Escape> key bindings and the value validation: accepting an integer, float or string, cancelling, rejecting a non-numeric value and rejecting a value outside the allowed range. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
1 parent aa71eb2 commit 706238e

1 file changed

Lines changed: 64 additions & 2 deletions

File tree

Lib/test/test_tkinter/test_simpledialog.py

Lines changed: 64 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
import unittest
22
import tkinter
3+
from tkinter import messagebox
34
from test.support import requires, swap_attr
45
from test.test_tkinter.support import setUpModule # noqa: F401
5-
from test.test_tkinter.support import AbstractDefaultRootTest
6-
from tkinter.simpledialog import Dialog, askinteger
6+
from test.test_tkinter.support import AbstractDefaultRootTest, AbstractTkTest
7+
from tkinter.simpledialog import (Dialog, askinteger,
8+
_QueryInteger, _QueryFloat, _QueryString)
79

810
requires('gui')
911

@@ -32,5 +34,65 @@ def mock_wait_window(w):
3234
self.assertRaises(RuntimeError, askinteger, "Go To Line", "Line number")
3335

3436

37+
class QueryDialogTest(AbstractTkTest, unittest.TestCase):
38+
# The query dialogs are modal: their __init__ blocks in wait_window().
39+
# Mock that out so the dialog stays alive and can be driven with generated
40+
# events, exercising the <Return>/<Escape> bindings and the validation.
41+
42+
def open(self, query, **kw):
43+
with swap_attr(Dialog, 'wait_window', staticmethod(lambda w: None)):
44+
d = query("Title", "Prompt", parent=self.root, **kw)
45+
self.addCleanup(lambda: d.winfo_exists() and d.destroy())
46+
d.focus_force()
47+
d.update()
48+
return d
49+
50+
def enter(self, d, value, key='<Return>'):
51+
d.entry.delete(0, 'end')
52+
d.entry.insert(0, value)
53+
d.event_generate(key)
54+
d.update()
55+
56+
def test_return_accepts(self):
57+
for query, value, expected in [
58+
(_QueryInteger, '42', 42),
59+
(_QueryFloat, '1.5', 1.5),
60+
(_QueryString, 'spam', 'spam'),
61+
]:
62+
with self.subTest(query=query.__name__):
63+
d = self.open(query)
64+
self.enter(d, value)
65+
self.assertEqual(d.result, expected)
66+
self.assertFalse(d.winfo_exists()) # The dialog closed.
67+
68+
def test_escape_cancels(self):
69+
d = self.open(_QueryString)
70+
self.enter(d, 'spam', '<Escape>')
71+
self.assertIsNone(d.result)
72+
self.assertFalse(d.winfo_exists())
73+
74+
def test_invalid_value(self):
75+
warnings = []
76+
d = self.open(_QueryInteger)
77+
with swap_attr(messagebox, 'showwarning',
78+
lambda *a, **k: warnings.append(a)):
79+
self.enter(d, 'not a number')
80+
self.assertIsNone(d.result)
81+
self.assertTrue(d.winfo_exists()) # The dialog stays open.
82+
self.assertTrue(warnings)
83+
84+
def test_out_of_range(self):
85+
warnings = []
86+
d = self.open(_QueryInteger, minvalue=10, maxvalue=20)
87+
with swap_attr(messagebox, 'showwarning',
88+
lambda *a, **k: warnings.append(a)):
89+
self.enter(d, '5') # Below the minimum.
90+
self.assertIsNone(d.result)
91+
self.enter(d, '25') # Above the maximum.
92+
self.assertIsNone(d.result)
93+
self.assertTrue(d.winfo_exists())
94+
self.assertEqual(len(warnings), 2)
95+
96+
3597
if __name__ == "__main__":
3698
unittest.main()

0 commit comments

Comments
 (0)