| import sys | |
| import unittest | |
| import tkinter | |
| from tkinter import messagebox, ttk | |
| from test.support import requires, swap_attr | |
| from test.test_tkinter.support import setUpModule # noqa: F401 | |
| from test.test_tkinter.support import AbstractDefaultRootTest, AbstractDialogTest | |
| from tkinter.simpledialog import (Dialog, SimpleDialog, | |
| askinteger, askfloat, askstring, | |
| _QueryInteger, _QueryFloat, _QueryString, | |
| _underline_ampersand, _find_alt_key_target) | |
| requires('gui') | |
| class SimpleDialogTest(AbstractDialogTest, 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 | |
| # --- Widget set and appearance --- | |
| def test_use_ttk(self): | |
| # By default SimpleDialog uses the themed (ttk) widgets (tk::MessageBox). | |
| d = self.create(buttons=['OK'], bitmap='warning') | |
| self.assertEqual(d._buttons[0].winfo_class(), 'TButton') | |
| self.assertEqual(d.message.winfo_class(), 'TLabel') | |
| self.assertEqual(d.message.cget('anchor'), 'nw') # cf. MessageBox | |
| # The standard icons are drawn with themed images (cf. MessageBox). | |
| self.assertEqual(d.bitmap.winfo_class(), 'TLabel') | |
| self.assertIn('::tk::icons::warning', str(d.bitmap.cget('image'))) | |
| # tk::MessageBox makes the buttons equal width. | |
| self.assertEqual( | |
| str(d.root.children['bot'].grid_columnconfigure(0)['uniform']), | |
| 'buttons') | |
| # The dialog uses the themed background colour (cf. MessageBox). | |
| self.assertEqual(str(d.root.cget('background')), | |
| ttk.Style(d.root).lookup('.', 'background')) | |
| # The bindings work with the themed buttons too. | |
| self.require_mapped(d.root) | |
| d.root.update() | |
| d._buttons[0].focus_force() | |
| d.root.event_generate('<Return>') | |
| d.root.update() | |
| self.assertEqual(d.num, 0) | |
| def test_use_classic(self): | |
| # use_ttk=False uses the classic Tk widgets, modelled on tk_dialog. | |
| d = self.create(buttons=['OK'], bitmap='warning', use_ttk=False) | |
| self.assertEqual(d._buttons[0].winfo_class(), 'Button') | |
| self.assertEqual(d.message.winfo_class(), 'Label') | |
| if d.root._windowingsystem == 'x11': | |
| self.assertEqual(d.frame.cget('relief'), 'raised') | |
| # tk_dialog does not make the buttons equal width. | |
| self.assertIsNone(d.root.children['bot'].grid_columnconfigure(0)['uniform']) | |
| # The bitmap is a classic monochrome label. | |
| self.assertEqual(str(d.bitmap.cget('bitmap')), 'warning') | |
| def test_class_name(self): | |
| # class_ sets the Tk class of the dialog window (default 'Dialog'). | |
| self.assertEqual(self.create().root.winfo_class(), 'Dialog') | |
| d = self.create(class_='MyDialog') | |
| self.assertEqual(d.root.winfo_class(), 'MyDialog') | |
| # --- Message, detail and bitmap content --- | |
| def test_no_detail(self): | |
| # Without a detail message the message label expands. | |
| d = self.create() | |
| self.assertIsNone(d.detail) | |
| self.assertEqual(d.message.pack_info()['expand'], | |
| 1 if self.wantobjects else '1') | |
| def test_detail(self): | |
| # The detail message is shown below the main message. | |
| d = self.create(detail='More information.') | |
| self.assertEqual(d.detail.winfo_class(), 'TLabel') | |
| self.assertEqual(str(d.detail.cget('text')), 'More information.') | |
| self.assertEqual(d.detail.cget('anchor'), 'nw') # cf. MessageBox | |
| # With a detail message it expands and the main message does not. | |
| self.assertEqual(d.message.pack_info()['expand'], | |
| 0 if self.wantobjects else '0') | |
| self.assertEqual(d.detail.pack_info()['expand'], | |
| 1 if self.wantobjects else '1') | |
| def test_bitmap_fallback(self): | |
| # A non-standard bitmap has no themed image, so even the ttk version | |
| # falls back to a classic bitmap label. | |
| d = self.create(buttons=['OK'], bitmap='questhead') | |
| self.assertEqual(d.bitmap.winfo_class(), 'Label') | |
| self.assertEqual(str(d.bitmap.cget('bitmap')), 'questhead') | |
| def test_bitmap_detail_layout(self): | |
| # The bitmap is packed first to claim the whole left side; the message | |
| # and detail stack on its right, as in tk::MessageBox. | |
| d = self.create(buttons=['OK'], bitmap='warning', detail='More.') | |
| top = d.root.children['top'] | |
| layout = [(w.winfo_name(), str(w.pack_info()['side'])) | |
| for w in top.pack_slaves()] | |
| self.assertEqual(layout, | |
| [('bitmap', 'left'), ('msg', 'top'), ('dtl', 'top')]) | |
| # --- Buttons and keyboard --- | |
| def test_button_options(self): | |
| # A button entry can be a mapping of options, not just a label (like | |
| # the "[name ?-option value ...?]" button specs in tk::MessageBox). | |
| d = self.create(buttons=['Yes', | |
| {'text': 'No', 'underline': 0, 'width': 12}]) | |
| yes, no = d._buttons | |
| self.assertEqual(str(yes.cget('text')), 'Yes') | |
| self.assertEqual(str(no.cget('text')), 'No') | |
| self.assertEqual(no.cget('underline'), 0 if self.wantobjects else '0') | |
| self.assertEqual(str(no.cget('width')), '12') | |
| # The dialog still controls the default ring (default=0) ... | |
| self.assertEqual(yes.cget('default'), 'active') | |
| self.assertEqual(no.cget('default'), 'normal') | |
| # ... and the command, which records the button index. | |
| no.invoke() | |
| self.assertEqual(d.num, 1) | |
| def test_default_ring(self): | |
| # The default ring follows the keyboard focus among the buttons | |
| # (cf. tk::MessageBox). | |
| d = self.create() # buttons ['Yes', 'No'], default 0 | |
| b0, b1 = d._buttons | |
| self.assertEqual(b1.cget('default'), 'normal') | |
| b1.focus_force() | |
| d.root.update() | |
| self.assertEqual(b1.cget('default'), 'active') # focused -> ring | |
| b0.focus_force() | |
| d.root.update() | |
| self.assertEqual(b1.cget('default'), 'normal') # unfocused -> none | |
| def test_alt_key(self): | |
| # Alt + an underlined character (the "underline" button option) invokes | |
| # the matching button (cf. tk::AmpWidget in tk::MessageBox). | |
| d = self.create(buttons=['Yes', {'text': 'No', 'underline': 0}]) | |
| self.require_mapped(d.root) | |
| d.root.update() | |
| d._buttons[0].focus_force() | |
| d.root.event_generate('<Alt-n>') # "No" -> underline 0 -> "N" | |
| d.root.update() | |
| self.assertEqual(d.num, 1) | |
| def test_return_invokes_focused_button(self): | |
| # <Return> invokes the button with the focus, even if it is not the | |
| # default and the focus was not moved by keyboard traversal. | |
| d = self.create(buttons=['Yes', 'No']) # default 0 | |
| self.require_mapped(d.root) | |
| d.root.update() | |
| d._buttons[1].focus_force() | |
| d.root.event_generate('<Return>') | |
| d.root.update() | |
| self.assertEqual(d.num, 1) | |
| def test_focus_next_then_return(self): | |
| # <Tab> moves the focus to the next button; <Return> invokes it. | |
| d = self.create(buttons=['Yes', 'No']) | |
| self.require_mapped(d.root) | |
| d.root.update() | |
| d._buttons[0].focus_force() | |
| d._buttons[0].event_generate('<Tab>') | |
| d.root.update() | |
| d.root.event_generate('<Return>') | |
| d.root.update() | |
| self.assertEqual(d.num, 1) | |
| def test_focus_prev_then_return(self): | |
| # <Shift-Tab> moves the focus to the previous button. | |
| d = self.create(buttons=['Yes', 'No']) | |
| self.require_mapped(d.root) | |
| d.root.update() | |
| d._buttons[1].focus_force() | |
| d._buttons[1].event_generate('<Shift-Tab>') | |
| d.root.update() | |
| d.root.event_generate('<Return>') | |
| d.root.update() | |
| self.assertEqual(d.num, 0) | |
| def test_return_activates_default(self): | |
| # <Return> with the focus off the buttons invokes the default button. | |
| d = self.create() # default 0 | |
| self.require_mapped(d.root) | |
| d.root.update() | |
| d.root.focus_force() # the dialog, not a button, has the focus | |
| d.root.event_generate('<Return>') | |
| d.root.update() | |
| self.assertEqual(d.num, 0) | |
| def test_return_no_default(self): | |
| # With no default button, <Return> off the buttons rings the bell and | |
| # leaves the dialog open instead of activating a button. | |
| d = self.create(default=None) | |
| self.require_mapped(d.root) | |
| d.root.focus_force() # the dialog, not a button, has the focus | |
| 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()) | |
| # --- Modal lifecycle --- | |
| def test_destroy_cancels(self): | |
| # Destroying the window records the cancel index. | |
| d = self.create() | |
| d.root.update() | |
| d.root.destroy() | |
| self.assertEqual(d.num, 1) | |
| 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._buttons[0].invoke()) | |
| self.assertEqual(d.go(), 0) | |
| def test_go_foreign_grab(self): | |
| # gh-157676: the grab and the focus can be in a window which was not | |
| # created by tkinter, such as a native message box; they must be | |
| # restored after the dialog. | |
| tk = self.root.tk | |
| tk.call('toplevel', '.foreign') | |
| self.addCleanup(tk.call, 'destroy', '.foreign') | |
| tk.call('wm', 'deiconify', '.foreign') | |
| tk.call('update') | |
| tk.call('grab', 'set', '.foreign') | |
| tk.call('focus', '-force', '.foreign') | |
| d = self.create() | |
| d.root.after(1, lambda: d._buttons[0].invoke()) | |
| self.assertEqual(d.go(), 0) | |
| self.assertEqual(tk.call('grab', 'current', self.root._w), '.foreign') | |
| # On Windows the application can lose the focus when the dialog is | |
| # destroyed, and then "focus" returns an empty string. | |
| if sys.platform != 'win32': | |
| self.assertEqual(tk.call('focus'), '.foreign') | |
| class DialogTest(AbstractDialogTest, unittest.TestCase): | |
| # Dialog's button box is modelled on tk::MessageBox. | |
| 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 | |
| # --- Widget set and appearance --- | |
| def test_use_ttk(self): | |
| # The query dialogs use the themed (ttk) widgets by default. | |
| d = self.open() | |
| self.assertEqual(d.children['ok'].winfo_class(), 'TButton') | |
| self.assertEqual(d.entry.winfo_class(), 'TEntry') | |
| # tk::MessageBox makes the buttons equal width. | |
| self.assertEqual( | |
| str(d.children['bot'].grid_columnconfigure(0)['uniform']), 'buttons') | |
| def test_use_classic(self): | |
| # use_ttk=False uses the classic Tk widgets, modelled on tk_dialog. | |
| d = self.open(use_ttk=False) | |
| self.assertEqual(d.children['ok'].winfo_class(), 'Button') | |
| self.assertEqual(d.entry.winfo_class(), 'Entry') | |
| if d._windowingsystem == 'x11': | |
| self.assertEqual(d.children['bot'].cget('relief'), 'raised') | |
| # tk_dialog does not make the buttons equal width. | |
| self.assertIsNone(d.children['bot'].grid_columnconfigure(0)['uniform']) | |
| # The bindings work with the classic buttons too. | |
| invoked = [] | |
| cancel = d.children['cancel'] | |
| cancel.configure(command=lambda: invoked.append(True)) | |
| d.update() | |
| cancel.focus_force() | |
| d.event_generate('<Return>') | |
| d.update() | |
| self.assertTrue(invoked) | |
| def test_background(self): | |
| # The ttk dialog adopts the ttk background, even a customized one, | |
| # while the classic dialog keeps the default Toplevel background. | |
| style = ttk.Style(self.root) | |
| old = style.lookup('.', 'background') | |
| style.configure('.', background='#123456') | |
| self.addCleanup(style.configure, '.', background=old) | |
| d = self.open() | |
| self.assertEqual(str(d.cget('background')), '#123456') | |
| d = self.open(use_ttk=False) | |
| ref = tkinter.Toplevel(self.root) | |
| self.addCleanup(ref.destroy) | |
| self.assertEqual(str(d.cget('background')), str(ref.cget('background'))) | |
| def test_base_classic_by_default(self): | |
| # The Dialog base defaults to classic widgets so that subclasses adding | |
| # classic widgets keep their look; only the query dialogs opt into ttk. | |
| class MyDialog(Dialog): | |
| def body(self, master): | |
| pass | |
| with swap_attr(Dialog, 'wait_window', staticmethod(lambda w: None)): | |
| d = MyDialog(self.root, 'Title') | |
| self.addCleanup(lambda: d.winfo_exists() and d.destroy()) | |
| self.assertEqual(d.children['ok'].winfo_class(), 'Button') | |
| # --- Buttons and keyboard --- | |
| def test_button_default(self): | |
| d = self.open() | |
| self.assertEqual(d.children['ok'].cget('default'), 'active') | |
| self.assertEqual(d.children['cancel'].cget('default'), 'normal') | |
| def test_underline_ampersand(self): | |
| self.assertEqual(_underline_ampersand('Yes'), ('Yes', -1)) | |
| self.assertEqual(_underline_ampersand('&Yes'), ('Yes', 0)) | |
| self.assertEqual(_underline_ampersand('Save &As'), ('Save As', 5)) | |
| self.assertEqual(_underline_ampersand('A&&B'), ('A&B', -1)) | |
| self.assertEqual(_underline_ampersand('&a&b'), ('ab', 0)) | |
| def test_button_accelerator(self): | |
| # The buttons' "&" accelerators are parsed (cf. tk::AmpWidget). | |
| d = self.open() | |
| ok = d.children['ok'] # "&OK" -> underline 0 -> "O" | |
| self.assertEqual(str(ok.cget('text')), 'OK') | |
| self.assertEqual(ok.cget('underline'), 0 if self.wantobjects else '0') | |
| def test_default_ring(self): | |
| # The default ring follows the keyboard focus among the buttons. | |
| d = self.open() | |
| cancel = d.children['cancel'] | |
| self.assertEqual(cancel.cget('default'), 'normal') | |
| cancel.focus_force() | |
| d.update() | |
| self.assertEqual(cancel.cget('default'), 'active') | |
| d.children['ok'].focus_force() | |
| d.update() | |
| self.assertEqual(cancel.cget('default'), 'normal') | |
| def test_find_alt_key_target(self): | |
| d = self.open() | |
| ok = d.children['ok'] # "&OK" -> "O" | |
| cancel = d.children['cancel'] # "&Cancel" -> "C" | |
| self.assertIs(_find_alt_key_target(d, 'o'), ok) | |
| self.assertIs(_find_alt_key_target(d, 'O'), ok) # case-insensitive | |
| self.assertIs(_find_alt_key_target(d, 'c'), cancel) | |
| self.assertIsNone(_find_alt_key_target(d, 'q')) | |
| def test_alt_key(self): | |
| # The accelerator key (Alt + the underlined letter) invokes the button: | |
| # <Alt-Key> -> _alt_key -> the button's <<AltUnderlined>> -> invoke. | |
| d = self.open() | |
| invoked = [] | |
| cancel = d.children['cancel'] # "&Cancel" | |
| cancel.configure(command=lambda: invoked.append(True)) | |
| d.update() | |
| d.focus_force() | |
| d.event_generate('<Alt-c>') | |
| d.update() | |
| self.assertTrue(invoked) | |
| def test_return_invokes_focused_button(self): | |
| # <Return> invokes the focused button. | |
| d = self.open() | |
| invoked = [] | |
| cancel = d.children['cancel'] | |
| cancel.configure(command=lambda: invoked.append(True)) | |
| d.update() | |
| cancel.focus_force() | |
| d.event_generate('<Return>') | |
| d.update() | |
| self.assertEqual(invoked, [True]) | |
| def test_focus_next_then_return(self): | |
| # <Tab> moves the focus to the next button; <Return> invokes it. | |
| d = self.open() | |
| invoked = [] | |
| for name in ('ok', 'cancel'): | |
| d.children[name].configure(command=lambda name=name: invoked.append(name)) | |
| ok = d.children['ok'] | |
| d.update() | |
| ok.focus_force() | |
| ok.event_generate('<Tab>') # OK -> Cancel | |
| d.update() | |
| d.event_generate('<Return>') | |
| d.update() | |
| self.assertEqual(invoked, ['cancel']) | |
| def test_focus_prev_then_return(self): | |
| # <Shift-Tab> moves the focus to the previous button. | |
| d = self.open() | |
| invoked = [] | |
| for name in ('ok', 'cancel'): | |
| d.children[name].configure(command=lambda name=name: invoked.append(name)) | |
| cancel = d.children['cancel'] | |
| d.update() | |
| cancel.focus_force() | |
| cancel.event_generate('<Shift-Tab>') # Cancel -> OK | |
| d.update() | |
| d.event_generate('<Return>') | |
| d.update() | |
| self.assertEqual(invoked, ['ok']) | |
| class DefaultRootTest(AbstractDefaultRootTest, unittest.TestCase): | |
| def test_askinteger(self): | |
| @staticmethod | |
| def mock_wait_window(w): | |
| nonlocal ismapped | |
| ismapped = w.master.winfo_ismapped() | |
| w.destroy() | |
| with swap_attr(Dialog, 'wait_window', mock_wait_window): | |
| ismapped = None | |
| askinteger("Go To Line", "Line number") | |
| self.assertEqual(ismapped, False) | |
| root = tkinter.Tk() | |
| ismapped = None | |
| askinteger("Go To Line", "Line number") | |
| self.assertEqual(ismapped, True) | |
| root.destroy() | |
| tkinter.NoDefaultRoot() | |
| self.assertRaises(RuntimeError, askinteger, "Go To Line", "Line number") | |
| 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. | |
| def open(self, query, **kw): | |
| with swap_attr(Dialog, 'wait_window', staticmethod(lambda w: None)): | |
| d = query("Title", "Prompt", parent=self.root, **kw) | |
| self.addCleanup(lambda: d.winfo_exists() and d.destroy()) | |
| d.focus_force() | |
| d.update() | |
| return d | |
| def enter(self, d, value, key='<Return>'): | |
| d.entry.delete(0, 'end') | |
| d.entry.insert(0, value) | |
| d.event_generate(key) | |
| d.update() | |
| # --- Prompt and entry --- | |
| def test_prompt_wraplength(self): | |
| # A long prompt wraps instead of widening the dialog (cf. MessageBox). | |
| d = self.open(_QueryInteger) | |
| body = d.children['top'] | |
| label = [w for w in body.winfo_children() if w is not d.entry][0] | |
| self.assertEqual(str(label.cget('wraplength')), '3i') | |
| def test_prompt_column_expands(self): | |
| # The prompt and entry column expands to the full width, like the | |
| # weighted message column in tk::MessageBox. | |
| d = self.open(_QueryInteger) | |
| body = d.children['top'] | |
| self.assertEqual(body.grid_columnconfigure(0)['weight'], 1) | |
| 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')), '*') | |
| # --- Accept and cancel --- | |
| def test_return_accepts(self): | |
| for query, value, expected in [ | |
| (_QueryInteger, '42', 42), | |
| (_QueryFloat, '1.5', 1.5), | |
| (_QueryString, 'spam', 'spam'), | |
| ]: | |
| with self.subTest(query=query.__name__): | |
| d = self.open(query) | |
| self.enter(d, value) | |
| self.assertEqual(d.result, expected) | |
| self.assertFalse(d.winfo_exists()) # The dialog closed. | |
| def test_escape_cancels(self): | |
| d = self.open(_QueryString) | |
| self.enter(d, 'spam', '<Escape>') | |
| self.assertIsNone(d.result) | |
| self.assertFalse(d.winfo_exists()) | |
| # --- Validation --- | |
| def test_invalid_value(self): | |
| warnings = [] | |
| d = self.open(_QueryInteger) | |
| with swap_attr(messagebox, 'showwarning', | |
| lambda *a, **k: warnings.append(a)): | |
| self.enter(d, 'not a number') | |
| self.assertIsNone(d.result) | |
| self.assertTrue(d.winfo_exists()) # The dialog stays open. | |
| self.assertTrue(warnings) | |
| def test_out_of_range(self): | |
| warnings = [] | |
| d = self.open(_QueryInteger, minvalue=10, maxvalue=20) | |
| with swap_attr(messagebox, 'showwarning', | |
| lambda *a, **k: warnings.append(a)): | |
| self.enter(d, '5') # Below the minimum. | |
| self.assertIsNone(d.result) | |
| self.enter(d, '25') # Above the maximum. | |
| self.assertIsNone(d.result) | |
| 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()) | |
| # --- Convenience ask* functions --- | |
| 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() |