# Common tests for test_tkinter/test_widgets.py and test_ttk/test_widgets.py import unittest import sys import tkinter from tkinter.ttk import setup_master, Scale from tkinter.test.support import (tcl_version, requires_tcl, get_tk_patchlevel, pixels_conv, tcl_obj_eq) import test.support noconv = False if get_tk_patchlevel() < (8, 5, 11): noconv = str pixels_round = round if get_tk_patchlevel()[:3] == (8, 5, 11): # Issue #19085: Workaround a bug in Tk # http://core.tcl.tk/tk/info/3497848 pixels_round = int _sentinel = object() class AbstractWidgetTest: _conv_pixels = staticmethod(pixels_round) _conv_pad_pixels = None wantobjects = True def setUp(self): self.root = setup_master() self.scaling = float(self.root.call('tk', 'scaling')) if not self.root.wantobjects(): self.wantobjects = False def create(self, **kwargs): widget = self._create(**kwargs) self.addCleanup(widget.destroy) return widget def assertEqual2(self, actual, expected, msg=None, eq=object.__eq__): if eq(actual, expected): return self.assertEqual(actual, expected, msg) def checkParam(self, widget, name, value, *, expected=_sentinel, conv=False, eq=None): widget[name] = value if expected is _sentinel: expected = value if conv: expected = conv(expected) if not self.wantobjects: if isinstance(expected, tuple): expected = tkinter._join(expected) else: expected = str(expected) if eq is None: eq = tcl_obj_eq self.assertEqual2(widget[name], expected, eq=eq) self.assertEqual2(widget.cget(name), expected, eq=eq) # XXX if not isinstance(widget, Scale): t = widget.configure(name) self.assertEqual(len(t), 5) self.assertEqual2(t[4], expected, eq=eq) def checkInvalidParam(self, widget, name, value, errmsg=None, *, keep_orig=True): orig = widget[name] if errmsg is not None: errmsg = errmsg.format(value) with self.assertRaises(tkinter.TclError) as cm: widget[name] = value if errmsg is not None: self.assertEqual(str(cm.exception), errmsg) if keep_orig: self.assertEqual(widget[name], orig) else: widget[name] = orig with self.assertRaises(tkinter.TclError) as cm: widget.configure({name: value}) if errmsg is not None: self.assertEqual(str(cm.exception), errmsg) if keep_orig: self.assertEqual(widget[name], orig) else: widget[name] = orig def checkParams(self, widget, name, *values, **kwargs): for value in values: self.checkParam(widget, name, value, **kwargs) def checkIntegerParam(self, widget, name, *values, **kwargs): self.checkParams(widget, name, *values, **kwargs) self.checkInvalidParam(widget, name, '', errmsg='expected integer but got ""') self.checkInvalidParam(widget, name, '10p', errmsg='expected integer but got "10p"') self.checkInvalidParam(widget, name, 3.2, errmsg='expected integer but got "3.2"') def checkFloatParam(self, widget, name, *values, conv=float, **kwargs): for value in values: self.checkParam(widget, name, value, conv=conv, **kwargs) self.checkInvalidParam(widget, name, '', errmsg='expected floating-point number but got ""') self.checkInvalidParam(widget, name, 'spam', errmsg='expected floating-point number but got "spam"') def checkBooleanParam(self, widget, name): for value in (False, 0, 'false', 'no', 'off'): self.checkParam(widget, name, value, expected=0) for value in (True, 1, 'true', 'yes', 'on'): self.checkParam(widget, name, value, expected=1) self.checkInvalidParam(widget, name, '', errmsg='expected boolean value but got ""') self.checkInvalidParam(widget, name, 'spam', errmsg='expected boolean value but got "spam"') def checkColorParam(self, widget, name, *, allow_empty=None, **kwargs): self.checkParams(widget, name, '#ff0000', '#00ff00', '#0000ff', '#123456', 'red', 'green', 'blue', 'white', 'black', 'grey', **kwargs) self.checkInvalidParam(widget, name, 'spam', errmsg='unknown color name "spam"') def checkCursorParam(self, widget, name, **kwargs): self.checkParams(widget, name, 'arrow', 'watch', 'cross', '',**kwargs) if tcl_version >= (8, 5): self.checkParam(widget, name, 'none') self.checkInvalidParam(widget, name, 'spam', errmsg='bad cursor spec "spam"') def checkCommandParam(self, widget, name): def command(*args): pass widget[name] = command self.assertTrue(widget[name]) self.checkParams(widget, name, '') def checkEnumParam(self, widget, name, *values, errmsg=None, **kwargs): self.checkParams(widget, name, *values, **kwargs) if errmsg is None: errmsg2 = ' %s "{}": must be %s%s or %s' % ( name, ', '.join(values[:-1]), ',' if len(values) > 2 else '', values[-1]) self.checkInvalidParam(widget, name, '', errmsg='ambiguous' + errmsg2) errmsg = 'bad' + errmsg2 self.checkInvalidParam(widget, name, 'spam', errmsg=errmsg) def checkPixelsParam(self, widget, name, *values, conv=None, keep_orig=True, **kwargs): if conv is None: conv = self._conv_pixels for value in values: expected = _sentinel conv1 = conv if isinstance(value, str): if conv1 and conv1 is not str: expected = pixels_conv(value) * self.scaling conv1 = round self.checkParam(widget, name, value, expected=expected, conv=conv1, **kwargs) self.checkInvalidParam(widget, name, '6x', errmsg='bad screen distance "6x"', keep_orig=keep_orig) self.checkInvalidParam(widget, name, 'spam', errmsg='bad screen distance "spam"', keep_orig=keep_orig) def checkReliefParam(self, widget, name): self.checkParams(widget, name, 'flat', 'groove', 'raised', 'ridge', 'solid', 'sunken') errmsg='bad relief "spam": must be '\ 'flat, groove, raised, ridge, solid, or sunken' if tcl_version < (8, 6): errmsg = None self.checkInvalidParam(widget, name, 'spam', errmsg=errmsg) def checkImageParam(self, widget, name): image = tkinter.PhotoImage('image1') self.checkParam(widget, name, image, conv=str) self.checkInvalidParam(widget, name, 'spam', errmsg='image "spam" doesn\'t exist') widget[name] = '' def checkVariableParam(self, widget, name, var): self.checkParam(widget, name, var, conv=str) class StandardOptionsTests: STANDARD_OPTIONS = ( 'activebackground', 'activeborderwidth', 'activeforeground', 'anchor', 'background', 'bitmap', 'borderwidth', 'compound', 'cursor', 'disabledforeground', 'exportselection', 'font', 'foreground', 'highlightbackground', 'highlightcolor', 'highlightthickness', 'image', 'insertbackground', 'insertborderwidth', 'insertofftime', 'insertontime', 'insertwidth', 'jump', 'justify', 'orient', 'padx', 'pady', 'relief', 'repeatdelay', 'repeatinterval', 'selectbackground', 'selectborderwidth', 'selectforeground', 'setgrid', 'takefocus', 'text', 'textvariable', 'troughcolor', 'underline', 'wraplength', 'xscrollcommand', 'yscrollcommand', ) def test_activebackground(self): widget = self.create() self.checkColorParam(widget, 'activebackground') def test_activeborderwidth(self): widget = self.create() self.checkPixelsParam(widget, 'activeborderwidth', 0, 1.3, 2.9, 6, -2, '10p') def test_activeforeground(self): widget = self.create() self.checkColorParam(widget, 'activeforeground') def test_anchor(self): widget = self.create() self.checkEnumParam(widget, 'anchor', 'n', 'ne', 'e', 'se', 's', 'sw', 'w', 'nw', 'center') def test_background(self): widget = self.create() self.checkColorParam(widget, 'background') if 'bg' in self.OPTIONS: self.checkColorParam(widget, 'bg') def test_bitmap(self): widget = self.create() self.checkParam(widget, 'bitmap', 'questhead') self.checkParam(widget, 'bitmap', 'gray50') filename = test.support.findfile('python.xbm', subdir='imghdrdata') self.checkParam(widget, 'bitmap', '@' + filename) # Cocoa Tk widgets don't detect invalid -bitmap values # See https://core.tcl.tk/tk/info/31cd33dbf0 if not ('aqua' in self.root.tk.call('tk', 'windowingsystem') and 'AppKit' in self.root.winfo_server()): self.checkInvalidParam(widget, 'bitmap', 'spam', errmsg='bitmap "spam" not defined') def test_borderwidth(self): widget = self.create() self.checkPixelsParam(widget, 'borderwidth', 0, 1.3, 2.6, 6, -2, '10p') if 'bd' in self.OPTIONS: self.checkPixelsParam(widget, 'bd', 0, 1.3, 2.6, 6, -2, '10p') def test_compound(self): widget = self.create() self.checkEnumParam(widget, 'compound', 'bottom', 'center', 'left', 'none', 'right', 'top') def test_cursor(self): widget = self.create() self.checkCursorParam(widget, 'cursor') def test_disabledforeground(self): widget = self.create() self.checkColorParam(widget, 'disabledforeground') def test_exportselection(self): widget = self.create() self.checkBooleanParam(widget, 'exportselection') def test_font(self): widget = self.create() self.checkParam(widget, 'font', '-Adobe-Helvetica-Medium-R-Normal--*-120-*-*-*-*-*-*') self.checkInvalidParam(widget, 'font', '', errmsg='font "" doesn\'t exist') def test_foreground(self): widget = self.create() self.checkColorParam(widget, 'foreground') if 'fg' in self.OPTIONS: self.checkColorParam(widget, 'fg') def test_highlightbackground(self): widget = self.create() self.checkColorParam(widget, 'highlightbackground') def test_highlightcolor(self): widget = self.create() self.checkColorParam(widget, 'highlightcolor') def test_highlightthickness(self): widget = self.create() self.checkPixelsParam(widget, 'highlightthickness', 0, 1.3, 2.6, 6, '10p') self.checkParam(widget, 'highlightthickness', -2, expected=0, conv=self._conv_pixels) @unittest.skipIf(sys.platform == 'darwin', 'crashes with Cocoa Tk (issue19733)') def test_image(self): widget = self.create() self.checkImageParam(widget, 'image') def test_insertbackground(self): widget = self.create() self.checkColorParam(widget, 'insertbackground') def test_insertborderwidth(self): widget = self.create() self.checkPixelsParam(widget, 'insertborderwidth', 0, 1.3, 2.6, 6, -2, '10p') def test_insertofftime(self): widget = self.create() self.checkIntegerParam(widget, 'insertofftime', 100) def test_insertontime(self): widget = self.create() self.checkIntegerParam(widget, 'insertontime', 100) def test_insertwidth(self): widget = self.create() self.checkPixelsParam(widget, 'insertwidth', 1.3, 2.6, -2, '10p') def test_jump(self): widget = self.create() self.checkBooleanParam(widget, 'jump') def test_justify(self): widget = self.create() self.checkEnumParam(widget, 'justify', 'left', 'right', 'center', errmsg='bad justification "{}": must be ' 'left, right, or center') self.checkInvalidParam(widget, 'justify', '', errmsg='ambiguous justification "": must be ' 'left, right, or center') def test_orient(self): widget = self.create() self.assertEqual(str(widget['orient']), self.default_orient) self.checkEnumParam(widget, 'orient', 'horizontal', 'vertical') def test_padx(self): widget = self.create() self.checkPixelsParam(widget, 'padx', 3, 4.4, 5.6, -2, '12m', conv=self._conv_pad_pixels) def test_pady(self): widget = self.create() self.checkPixelsParam(widget, 'pady', 3, 4.4, 5.6, -2, '12m', conv=self._conv_pad_pixels) def test_relief(self): widget = self.create() self.checkReliefParam(widget, 'relief') def test_repeatdelay(self): widget = self.create() self.checkIntegerParam(widget, 'repeatdelay', -500, 500) def test_repeatinterval(self): widget = self.create() self.checkIntegerParam(widget, 'repeatinterval', -500, 500) def test_selectbackground(self): widget = self.create() self.checkColorParam(widget, 'selectbackground') def test_selectborderwidth(self): widget = self.create() self.checkPixelsParam(widget, 'selectborderwidth', 1.3, 2.6, -2, '10p') def test_selectforeground(self): widget = self.create() self.checkColorParam(widget, 'selectforeground') def test_setgrid(self): widget = self.create() self.checkBooleanParam(widget, 'setgrid') def test_state(self): widget = self.create() self.checkEnumParam(widget, 'state', 'active', 'disabled', 'normal') def test_takefocus(self): widget = self.create() self.checkParams(widget, 'takefocus', '0', '1', '') def test_text(self): widget = self.create() self.checkParams(widget, 'text', '', 'any string') def test_textvariable(self): widget = self.create() var = tkinter.StringVar() self.checkVariableParam(widget, 'textvariable', var) def test_troughcolor(self): widget = self.create() self.checkColorParam(widget, 'troughcolor') def test_underline(self): widget = self.create() self.checkIntegerParam(widget, 'underline', 0, 1, 10) def test_wraplength(self): widget = self.create() self.checkPixelsParam(widget, 'wraplength', 100) def test_xscrollcommand(self): widget = self.create() self.checkCommandParam(widget, 'xscrollcommand') def test_yscrollcommand(self): widget = self.create() self.checkCommandParam(widget, 'yscrollcommand') # non-standard but common options def test_command(self): widget = self.create() self.checkCommandParam(widget, 'command') def test_indicatoron(self): widget = self.create() self.checkBooleanParam(widget, 'indicatoron') def test_offrelief(self): widget = self.create() self.checkReliefParam(widget, 'offrelief') def test_overrelief(self): widget = self.create() self.checkReliefParam(widget, 'overrelief') def test_selectcolor(self): widget = self.create() self.checkColorParam(widget, 'selectcolor') def test_selectimage(self): widget = self.create() self.checkImageParam(widget, 'selectimage') @requires_tcl(8, 5) def test_tristateimage(self): widget = self.create() self.checkImageParam(widget, 'tristateimage') @requires_tcl(8, 5) def test_tristatevalue(self): widget = self.create() self.checkParam(widget, 'tristatevalue', 'unknowable') def test_variable(self): widget = self.create() var = tkinter.DoubleVar() self.checkVariableParam(widget, 'variable', var) class IntegerSizeTests: def test_height(self): widget = self.create() self.checkIntegerParam(widget, 'height', 100, -100, 0) def test_width(self): widget = self.create() self.checkIntegerParam(widget, 'width', 402, -402, 0) class PixelSizeTests: def test_height(self): widget = self.create() self.checkPixelsParam(widget, 'height', 100, 101.2, 102.6, -100, 0, '3c') def test_width(self): widget = self.create() self.checkPixelsParam(widget, 'width', 402, 403.4, 404.6, -402, 0, '5i') def add_standard_options(*source_classes): # This decorator adds test_xxx methods from source classes for every xxx # option in the OPTIONS class attribute if they are not defined explicitly. def decorator(cls): for option in cls.OPTIONS: methodname = 'test_' + option if not hasattr(cls, methodname): for source_class in source_classes: if hasattr(source_class, methodname): setattr(cls, methodname, getattr(source_class, methodname)) break else: def test(self, option=option): widget = self.create() widget[option] raise AssertionError('Option "%s" is not tested in %s' % (option, cls.__name__)) test.__name__ = methodname setattr(cls, methodname, test) return cls return decorator def setUpModule(): if test.support.verbose: tcl = tkinter.Tcl() print('patchlevel =', tcl.call('info', 'patchlevel'))