]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
[3.15] gh-151678: Add tests for tkinter.simpledialog (GH-151856) (GH-151869)
authorMiss Islington (bot) <31488909+miss-islington@users.noreply.github.com>
Sun, 21 Jun 2026 18:59:26 +0000 (20:59 +0200)
committerGitHub <noreply@github.com>
Sun, 21 Jun 2026 18:59:26 +0000 (18:59 +0000)
(cherry picked from commit f28ef858f5515d200319e9675d66f8f13afa5a0d)

Co-authored-by: Serhiy Storchaka <storchaka@gmail.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Lib/test/test_tkinter/test_simpledialog.py

index 6cf57fde8d4c56fc07b8ae003e2a63a74a3ffe3f..942b7ebf7120b3ac6a274b6e37394357e4ab27c7 100644 (file)
@@ -4,12 +4,143 @@ 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 tkinter.simpledialog import (Dialog, askinteger,
+from tkinter.simpledialog import (Dialog, SimpleDialog,
+                                  askinteger, askfloat, askstring,
                                   _QueryInteger, _QueryFloat, _QueryString)
 
 requires('gui')
 
 
+class SimpleDialogTest(AbstractTkTest, unittest.TestCase):
+    # SimpleDialog's modal loop is in go(); its bindings are exercised here by
+    # generating events on the constructed dialog, without entering the loop.
+
+    def create(self, **kw):
+        kw.setdefault('text', 'Question?')
+        kw.setdefault('buttons', ['Yes', 'No'])
+        kw.setdefault('default', 0)
+        kw.setdefault('cancel', 1)
+        d = SimpleDialog(self.root, **kw)
+        self.addCleanup(lambda: d.root.winfo_exists() and d.root.destroy())
+        return d
+
+    def test_message(self):
+        # The text is shown in a message widget.
+        d = self.create(text='Hello?')
+        self.assertEqual(d.message.winfo_class(), 'Message')
+        self.assertEqual(str(d.message.cget('text')), 'Hello?')
+
+    def test_class_name(self):
+        # class_ sets the Tk class of the dialog window.
+        d = self.create(class_='MyDialog')
+        self.assertEqual(d.root.winfo_class(), 'MyDialog')
+
+    def test_button(self):
+        # Pressing a button records its index.
+        d = self.create(buttons=['Yes', 'No'])
+        d.frame.winfo_children()[1].invoke()  # "No"
+        self.assertEqual(d.num, 1)
+
+    def test_default_button(self):
+        # The default button is drawn with a raised border.
+        d = self.create(buttons=['Yes', 'No'], default=0)
+        self.assertEqual(str(d.frame.winfo_children()[0].cget('relief')), 'ridge')
+
+    def test_return_activates_default(self):
+        # <Return> invokes the default button.
+        d = self.create()  # default 0
+        d.root.focus_force()
+        d.root.update()
+        d.root.event_generate('<Return>')
+        d.root.update()
+        self.assertEqual(d.num, 0)
+
+    def test_return_no_default(self):
+        # With no default button, <Return> rings the bell and leaves the dialog
+        # open instead of activating a button.
+        d = self.create(default=None)
+        d.root.focus_force()
+        d.root.update()
+        bells = []
+        with swap_attr(d.root, 'bell', lambda *a, **k: bells.append(True)):
+            d.root.event_generate('<Return>')
+            d.root.update()
+        self.assertTrue(bells)  # rang the bell
+        self.assertIsNone(d.num)
+        self.assertTrue(d.root.winfo_exists())
+
+    def test_wm_delete_cancels(self):
+        # Closing the window through the window manager records the cancel index.
+        d = self.create()  # cancel 1
+        d.wm_delete_window()
+        self.assertEqual(d.num, 1)
+
+    def test_wm_delete_no_cancel(self):
+        # With no cancel index, closing the window through the window manager
+        # rings the bell and leaves the dialog open instead of recording an
+        # index.
+        d = self.create(default=None, cancel=None)
+        d.root.update()
+        bells = []
+        with swap_attr(d.root, 'bell', lambda *a, **k: bells.append(True)):
+            d.wm_delete_window()
+            d.root.update()
+        self.assertTrue(bells)  # rang the bell
+        self.assertIsNone(d.num)
+        self.assertTrue(d.root.winfo_exists())
+
+    def test_go(self):
+        # go() runs the modal loop and returns the chosen button's index.
+        d = self.create()
+        d.root.after(1, lambda: d.done(0))
+        self.assertEqual(d.go(), 0)
+
+
+class DialogTest(AbstractTkTest, unittest.TestCase):
+    # Dialog is a base class for custom dialogs; exercise it via _QueryInteger.
+
+    def open(self, **kw):
+        with swap_attr(Dialog, 'wait_window', staticmethod(lambda w: None)):
+            d = _QueryInteger('Title', 'Prompt', parent=self.root, **kw)
+        self.addCleanup(lambda: d.winfo_exists() and d.destroy())
+        return d
+
+    def buttons(self, d):
+        # Map the button box's buttons by their label.
+        return {str(b.cget('text')): b
+                for frame in d.winfo_children()
+                for b in frame.winfo_children()
+                if b.winfo_class() == 'Button'}
+
+    def test_background(self):
+        # The classic dialog keeps the default Toplevel background.
+        d = self.open()
+        ref = tkinter.Toplevel(self.root)
+        self.addCleanup(ref.destroy)
+        self.assertEqual(str(d.cget('background')), str(ref.cget('background')))
+
+    def test_buttons(self):
+        # The button box has OK (the default) and Cancel buttons.
+        buttons = self.buttons(self.open())
+        self.assertEqual(set(buttons), {'OK', 'Cancel'})
+        self.assertEqual(str(buttons['OK'].cget('default')), 'active')
+
+    def test_ok(self):
+        # The OK button validates the entry and stores the result.
+        d = self.open()
+        d.entry.insert(0, '42')
+        self.buttons(d)['OK'].invoke()
+        self.assertEqual(d.result, 42)
+        self.assertFalse(d.winfo_exists())  # The dialog closed.
+
+    def test_cancel(self):
+        # The Cancel button closes the dialog without a result.
+        d = self.open()
+        self.buttons(d)['Cancel'].invoke()
+        self.assertIsNone(d.result)
+        self.assertFalse(d.winfo_exists())
+
+
 class DefaultRootTest(AbstractDefaultRootTest, unittest.TestCase):
 
     def test_askinteger(self):
@@ -53,6 +184,19 @@ class QueryDialogTest(AbstractTkTest, unittest.TestCase):
         d.event_generate(key)
         d.update()
 
+    def test_initialvalue(self):
+        # The entry is pre-filled with the initial value, which is accepted.
+        d = self.open(_QueryInteger, initialvalue=42)
+        self.assertEqual(d.entry.get(), '42')
+        d.event_generate('<Return>')
+        d.update()
+        self.assertEqual(d.result, 42)
+
+    def test_show(self):
+        # _QueryString hides the entered text when show is given.
+        d = self.open(_QueryString, show='*')
+        self.assertEqual(str(d.entry.cget('show')), '*')
+
     def test_return_accepts(self):
         for query, value, expected in [
             (_QueryInteger, '42', 42),
@@ -93,6 +237,37 @@ class QueryDialogTest(AbstractTkTest, unittest.TestCase):
         self.assertTrue(d.winfo_exists())
         self.assertEqual(len(warnings), 2)
 
+    def test_boundary_values_accepted(self):
+        # The min/max checks are inclusive: a value equal to a bound passes.
+        d = self.open(_QueryInteger, minvalue=10, maxvalue=20)
+        self.enter(d, '10')   # Exactly the minimum.
+        self.assertEqual(d.result, 10)
+        self.assertFalse(d.winfo_exists())
+
+        d = self.open(_QueryInteger, minvalue=10, maxvalue=20)
+        self.enter(d, '20')   # Exactly the maximum.
+        self.assertEqual(d.result, 20)
+        self.assertFalse(d.winfo_exists())
+
+    def run_ask(self, ask, value, **kw):
+        # Drive a modal ask* function: enter a value and accept it.
+        def accept(d):
+            d.entry.delete(0, 'end')
+            d.entry.insert(0, value)
+            d.ok()
+        with swap_attr(Dialog, 'wait_window', staticmethod(accept)):
+            return ask('Title', 'Prompt', parent=self.root, **kw)
+
+    def test_ask_functions(self):
+        self.assertEqual(self.run_ask(askinteger, '42'), 42)
+        self.assertEqual(self.run_ask(askfloat, '1.5'), 1.5)
+        self.assertEqual(self.run_ask(askstring, 'spam'), 'spam')
+
+    def test_ask_cancelled(self):
+        # A cancelled ask* returns None.
+        with swap_attr(Dialog, 'wait_window', staticmethod(lambda d: d.cancel())):
+            self.assertIsNone(askstring('Title', 'Prompt', parent=self.root))
+
 
 if __name__ == "__main__":
     unittest.main()