| import os | |
| import unittest | |
| import tkinter | |
| from tkinter import filedialog | |
| from tkinter import ttk | |
| 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 AbstractDialogTest, AbstractTkTest | |
| requires('gui') | |
| class NativeDialogTest(AbstractTkTest, unittest.TestCase): | |
| # Open, SaveAs and Directory wrap modal native dialogs. The _test_callback | |
| # seam is called by show() just before the dialog would open; replacing it | |
| # with a function that raises exercises show() without blocking on the | |
| # dialog. | |
| def check(self, dialog_class, command): | |
| self.assertEqual(dialog_class.command, command) | |
| master = None | |
| def test_callback(dialog, parent): | |
| nonlocal master | |
| master = parent | |
| raise ZeroDivisionError | |
| with swap_attr(Dialog, '_test_callback', test_callback): | |
| d = dialog_class(self.root, title='Test') | |
| self.assertRaises(ZeroDivisionError, d.show) | |
| self.assertIs(master, self.root) | |
| def test_open(self): | |
| self.check(filedialog.Open, 'tk_getOpenFile') | |
| def test_saveas(self): | |
| self.check(filedialog.SaveAs, 'tk_getSaveFile') | |
| def test_directory(self): | |
| self.check(filedialog.Directory, 'tk_chooseDirectory') | |
| class CancelResultTest(AbstractTkTest, unittest.TestCase): | |
| # On cancellation Tcl may report the empty result as '', () or b'' | |
| # (gh-103878). _fixresult() normalizes it to the documented empty value: | |
| # '' for the filename dialogs and () for the multiple-selection dialog. | |
| def check(self, dialog, expected): | |
| for empty in ('', (), b''): | |
| with self.subTest(empty=empty): | |
| result = dialog._fixresult(self.root, empty) | |
| self.assertEqual(result, expected) | |
| self.assertIs(type(result), type(expected)) | |
| def test_open(self): | |
| self.check(filedialog.Open(self.root), '') | |
| def test_saveas(self): | |
| self.check(filedialog.SaveAs(self.root), '') | |
| def test_directory(self): | |
| self.check(filedialog.Directory(self.root), '') | |
| def test_openfilenames(self): | |
| self.check(filedialog.Open(self.root, multiple=1), ()) | |
| def test_results_preserved(self): | |
| # A real selection is returned unchanged. | |
| single = filedialog.Open(self.root) | |
| self.assertEqual(single._fixresult(self.root, '/a/spam'), '/a/spam') | |
| multiple = filedialog.Open(self.root, multiple=1) | |
| self.assertEqual(multiple._fixresult(self.root, ('/a', '/b')), | |
| ('/a', '/b')) | |
| 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. | |
| def test_selection(self): | |
| d = filedialog.FileDialog(self.root) | |
| d.directory = os.path.abspath(os.sep) | |
| d.set_selection('spam.txt') | |
| self.assertEqual(os.path.basename(d.get_selection()), 'spam.txt') | |
| def test_filter(self): | |
| d = filedialog.FileDialog(self.root) | |
| d.set_filter(os.getcwd(), '*.py') | |
| self.assertEqual(d.get_filter(), (os.getcwd(), '*.py')) | |
| def test_ok_cancel(self): | |
| d = filedialog.FileDialog(self.root) | |
| d.directory = os.getcwd() | |
| d.set_selection('spam.txt') | |
| d.ok_command() # Accepts the current selection. | |
| self.assertEqual(os.path.basename(d.how), 'spam.txt') | |
| d.cancel_command() # Returns no selection. | |
| self.assertIsNone(d.how) | |
| def test_subclasses(self): | |
| for cls in filedialog.LoadFileDialog, filedialog.SaveFileDialog: | |
| with self.subTest(cls=cls.__name__): | |
| d = cls(self.root) | |
| self.assertIsInstance(d, filedialog.FileDialog) | |
| self.assertEqual(d.top.title(), cls.title) | |
| # --- Themed widgets and keyboard (modernization) --- | |
| def open(self, **kw): | |
| d = filedialog.FileDialog(self.root, **kw) | |
| self.addCleanup(lambda: d.top.winfo_exists() and d.top.destroy()) | |
| d.top.deiconify() # __init__ leaves the dialog withdrawn until go() | |
| d.top.update() | |
| return d | |
| def test_use_ttk(self): | |
| # The dialog uses the themed (ttk) widgets by default. | |
| d = self.open() | |
| self.assertEqual(d.ok_button.winfo_class(), 'TButton') | |
| self.assertEqual(d.selection.winfo_class(), 'TEntry') | |
| def test_use_classic(self): | |
| # use_ttk=False uses the classic Tk widgets. | |
| d = self.open(use_ttk=False) | |
| self.assertEqual(d.ok_button.winfo_class(), 'Button') | |
| self.assertEqual(d.selection.winfo_class(), 'Entry') | |
| if d.top._windowingsystem == 'x11': | |
| self.assertEqual(d.botframe.cget('relief'), 'raised') | |
| 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.top.cget('background')), '#123456') | |
| d = self.open(use_ttk=False) | |
| ref = tkinter.Toplevel(self.root) | |
| self.addCleanup(ref.destroy) | |
| self.assertEqual(str(d.top.cget('background')), | |
| str(ref.cget('background'))) | |
| def test_button_accelerator(self): | |
| # The buttons' "&" accelerators are parsed. | |
| d = self.open() | |
| self.assertEqual(str(d.ok_button.cget('text')), 'OK') | |
| self.assertEqual(d.ok_button.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() | |
| self.assertEqual(d.cancel_button.cget('default'), 'normal') | |
| d.cancel_button.focus_force() | |
| d.top.update() | |
| self.assertEqual(d.cancel_button.cget('default'), 'active') | |
| d.ok_button.focus_force() | |
| d.top.update() | |
| self.assertEqual(d.cancel_button.cget('default'), 'normal') | |
| def test_alt_key(self): | |
| # Alt + the underlined letter invokes the matching button. | |
| d = self.open() | |
| invoked = [] | |
| d.cancel_button.configure(command=lambda: invoked.append(True)) | |
| d.top.update() | |
| d.top.focus_force() | |
| d.top.event_generate('<Alt-c>') # "&Cancel" | |
| d.top.update() | |
| self.assertTrue(invoked) | |
| def test_escape_cancels(self): | |
| # The Escape key cancels the dialog. | |
| d = self.open() | |
| d.how = 'spam' | |
| d.top.update() | |
| d.top.focus_force() | |
| d.top.event_generate('<Escape>') | |
| d.top.update() | |
| self.assertIsNone(d.how) | |
| def test_horizontal_scrollbars(self): | |
| # Each list has a horizontal scrollbar besides the vertical one. | |
| d = self.open() | |
| self.assertEqual(d.dirshbar.cget('orient'), 'horizontal') | |
| self.assertEqual(d.fileshbar.cget('orient'), 'horizontal') | |
| self.assertTrue(d.dirs.cget('xscrollcommand')) | |
| self.assertTrue(d.files.cget('xscrollcommand')) | |
| def test_type_ahead(self): | |
| # Typing characters over a list jumps to a matching entry. | |
| d = self.open() | |
| d.directory = os.getcwd() # browsing the match fills the selection entry | |
| d.files.delete(0, 'end') | |
| for name in ('alpha', 'bravo', 'charlie'): | |
| d.files.insert('end', name) | |
| d.top.update() | |
| # Force the focus right before generating the event: the window | |
| # manager can take it back. | |
| d.files.focus_force() | |
| d.files.event_generate('<Key>', keysym='c') | |
| d.top.update() | |
| sel = d.files.curselection() | |
| self.assertEqual([d.files.get(i) for i in sel], ['charlie']) | |
| class DeprecationTest(unittest.TestCase): | |
| def test_askopenfiles_deprecated(self): | |
| with swap_attr(filedialog, 'askopenfilenames', lambda **kw: ()): | |
| with self.assertWarns(DeprecationWarning): | |
| filedialog.askopenfiles() | |
| if __name__ == "__main__": | |
| unittest.main() |