Skip to content

Commit 2e4dddf

Browse files
miss-islingtonserhiy-storchakaclaude
authored
[3.15] gh-151678: Add tests for tkinter.simpledialog (GH-151856) (GH-151869)
(cherry picked from commit f28ef85) Co-authored-by: Serhiy Storchaka <storchaka@gmail.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
1 parent a2d2ee8 commit 2e4dddf

1 file changed

Lines changed: 176 additions & 1 deletion

File tree

Lib/test/test_tkinter/test_simpledialog.py

Lines changed: 176 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,143 @@
44
from test.support import requires, swap_attr
55
from test.test_tkinter.support import setUpModule # noqa: F401
66
from test.test_tkinter.support import AbstractDefaultRootTest, AbstractTkTest
7-
from tkinter.simpledialog import (Dialog, askinteger,
7+
from tkinter.simpledialog import (Dialog, SimpleDialog,
8+
askinteger, askfloat, askstring,
89
_QueryInteger, _QueryFloat, _QueryString)
910

1011
requires('gui')
1112

1213

14+
class SimpleDialogTest(AbstractTkTest, unittest.TestCase):
15+
# SimpleDialog's modal loop is in go(); its bindings are exercised here by
16+
# generating events on the constructed dialog, without entering the loop.
17+
18+
def create(self, **kw):
19+
kw.setdefault('text', 'Question?')
20+
kw.setdefault('buttons', ['Yes', 'No'])
21+
kw.setdefault('default', 0)
22+
kw.setdefault('cancel', 1)
23+
d = SimpleDialog(self.root, **kw)
24+
self.addCleanup(lambda: d.root.winfo_exists() and d.root.destroy())
25+
return d
26+
27+
def test_message(self):
28+
# The text is shown in a message widget.
29+
d = self.create(text='Hello?')
30+
self.assertEqual(d.message.winfo_class(), 'Message')
31+
self.assertEqual(str(d.message.cget('text')), 'Hello?')
32+
33+
def test_class_name(self):
34+
# class_ sets the Tk class of the dialog window.
35+
d = self.create(class_='MyDialog')
36+
self.assertEqual(d.root.winfo_class(), 'MyDialog')
37+
38+
def test_button(self):
39+
# Pressing a button records its index.
40+
d = self.create(buttons=['Yes', 'No'])
41+
d.frame.winfo_children()[1].invoke() # "No"
42+
self.assertEqual(d.num, 1)
43+
44+
def test_default_button(self):
45+
# The default button is drawn with a raised border.
46+
d = self.create(buttons=['Yes', 'No'], default=0)
47+
self.assertEqual(str(d.frame.winfo_children()[0].cget('relief')), 'ridge')
48+
49+
def test_return_activates_default(self):
50+
# <Return> invokes the default button.
51+
d = self.create() # default 0
52+
d.root.focus_force()
53+
d.root.update()
54+
d.root.event_generate('<Return>')
55+
d.root.update()
56+
self.assertEqual(d.num, 0)
57+
58+
def test_return_no_default(self):
59+
# With no default button, <Return> rings the bell and leaves the dialog
60+
# open instead of activating a button.
61+
d = self.create(default=None)
62+
d.root.focus_force()
63+
d.root.update()
64+
bells = []
65+
with swap_attr(d.root, 'bell', lambda *a, **k: bells.append(True)):
66+
d.root.event_generate('<Return>')
67+
d.root.update()
68+
self.assertTrue(bells) # rang the bell
69+
self.assertIsNone(d.num)
70+
self.assertTrue(d.root.winfo_exists())
71+
72+
def test_wm_delete_cancels(self):
73+
# Closing the window through the window manager records the cancel index.
74+
d = self.create() # cancel 1
75+
d.wm_delete_window()
76+
self.assertEqual(d.num, 1)
77+
78+
def test_wm_delete_no_cancel(self):
79+
# With no cancel index, closing the window through the window manager
80+
# rings the bell and leaves the dialog open instead of recording an
81+
# index.
82+
d = self.create(default=None, cancel=None)
83+
d.root.update()
84+
bells = []
85+
with swap_attr(d.root, 'bell', lambda *a, **k: bells.append(True)):
86+
d.wm_delete_window()
87+
d.root.update()
88+
self.assertTrue(bells) # rang the bell
89+
self.assertIsNone(d.num)
90+
self.assertTrue(d.root.winfo_exists())
91+
92+
def test_go(self):
93+
# go() runs the modal loop and returns the chosen button's index.
94+
d = self.create()
95+
d.root.after(1, lambda: d.done(0))
96+
self.assertEqual(d.go(), 0)
97+
98+
99+
class DialogTest(AbstractTkTest, unittest.TestCase):
100+
# Dialog is a base class for custom dialogs; exercise it via _QueryInteger.
101+
102+
def open(self, **kw):
103+
with swap_attr(Dialog, 'wait_window', staticmethod(lambda w: None)):
104+
d = _QueryInteger('Title', 'Prompt', parent=self.root, **kw)
105+
self.addCleanup(lambda: d.winfo_exists() and d.destroy())
106+
return d
107+
108+
def buttons(self, d):
109+
# Map the button box's buttons by their label.
110+
return {str(b.cget('text')): b
111+
for frame in d.winfo_children()
112+
for b in frame.winfo_children()
113+
if b.winfo_class() == 'Button'}
114+
115+
def test_background(self):
116+
# The classic dialog keeps the default Toplevel background.
117+
d = self.open()
118+
ref = tkinter.Toplevel(self.root)
119+
self.addCleanup(ref.destroy)
120+
self.assertEqual(str(d.cget('background')), str(ref.cget('background')))
121+
122+
def test_buttons(self):
123+
# The button box has OK (the default) and Cancel buttons.
124+
buttons = self.buttons(self.open())
125+
self.assertEqual(set(buttons), {'OK', 'Cancel'})
126+
self.assertEqual(str(buttons['OK'].cget('default')), 'active')
127+
128+
def test_ok(self):
129+
# The OK button validates the entry and stores the result.
130+
d = self.open()
131+
d.entry.insert(0, '42')
132+
self.buttons(d)['OK'].invoke()
133+
self.assertEqual(d.result, 42)
134+
self.assertFalse(d.winfo_exists()) # The dialog closed.
135+
136+
def test_cancel(self):
137+
# The Cancel button closes the dialog without a result.
138+
d = self.open()
139+
self.buttons(d)['Cancel'].invoke()
140+
self.assertIsNone(d.result)
141+
self.assertFalse(d.winfo_exists())
142+
143+
13144
class DefaultRootTest(AbstractDefaultRootTest, unittest.TestCase):
14145

15146
def test_askinteger(self):
@@ -53,6 +184,19 @@ def enter(self, d, value, key='<Return>'):
53184
d.event_generate(key)
54185
d.update()
55186

187+
def test_initialvalue(self):
188+
# The entry is pre-filled with the initial value, which is accepted.
189+
d = self.open(_QueryInteger, initialvalue=42)
190+
self.assertEqual(d.entry.get(), '42')
191+
d.event_generate('<Return>')
192+
d.update()
193+
self.assertEqual(d.result, 42)
194+
195+
def test_show(self):
196+
# _QueryString hides the entered text when show is given.
197+
d = self.open(_QueryString, show='*')
198+
self.assertEqual(str(d.entry.cget('show')), '*')
199+
56200
def test_return_accepts(self):
57201
for query, value, expected in [
58202
(_QueryInteger, '42', 42),
@@ -93,6 +237,37 @@ def test_out_of_range(self):
93237
self.assertTrue(d.winfo_exists())
94238
self.assertEqual(len(warnings), 2)
95239

240+
def test_boundary_values_accepted(self):
241+
# The min/max checks are inclusive: a value equal to a bound passes.
242+
d = self.open(_QueryInteger, minvalue=10, maxvalue=20)
243+
self.enter(d, '10') # Exactly the minimum.
244+
self.assertEqual(d.result, 10)
245+
self.assertFalse(d.winfo_exists())
246+
247+
d = self.open(_QueryInteger, minvalue=10, maxvalue=20)
248+
self.enter(d, '20') # Exactly the maximum.
249+
self.assertEqual(d.result, 20)
250+
self.assertFalse(d.winfo_exists())
251+
252+
def run_ask(self, ask, value, **kw):
253+
# Drive a modal ask* function: enter a value and accept it.
254+
def accept(d):
255+
d.entry.delete(0, 'end')
256+
d.entry.insert(0, value)
257+
d.ok()
258+
with swap_attr(Dialog, 'wait_window', staticmethod(accept)):
259+
return ask('Title', 'Prompt', parent=self.root, **kw)
260+
261+
def test_ask_functions(self):
262+
self.assertEqual(self.run_ask(askinteger, '42'), 42)
263+
self.assertEqual(self.run_ask(askfloat, '1.5'), 1.5)
264+
self.assertEqual(self.run_ask(askstring, 'spam'), 'spam')
265+
266+
def test_ask_cancelled(self):
267+
# A cancelled ask* returns None.
268+
with swap_attr(Dialog, 'wait_window', staticmethod(lambda d: d.cancel())):
269+
self.assertIsNone(askstring('Title', 'Prompt', parent=self.root))
270+
96271

97272
if __name__ == "__main__":
98273
unittest.main()

0 commit comments

Comments
 (0)