mirror of
https://github.com/python/cpython.git
synced 2026-08-01 22:55:03 -04:00
Opening several files at once is error-prone, and the returned list cannot be used in a "with" statement. Iterate over the names returned by askopenfilenames() and open them one by one instead. Co-Authored-By: Claude Opus 4.8 <[email protected]>
216 lines
8.0 KiB
Python
216 lines
8.0 KiB
Python
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 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(AbstractTkTest, 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.focus_force()
|
|
d.top.update()
|
|
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.focus_force()
|
|
d.top.update()
|
|
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.files.focus_force()
|
|
d.top.update()
|
|
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()
|