Home | History | Annotate | Download | only in test
      1 # Common tests for test_tkinter/test_widgets.py and test_ttk/test_widgets.py
      2 
      3 import unittest
      4 import sys
      5 import tkinter
      6 from tkinter.ttk import Scale
      7 from tkinter.test.support import (AbstractTkTest, tcl_version, requires_tcl,
      8                                   get_tk_patchlevel, pixels_conv, tcl_obj_eq)
      9 import test.support
     10 
     11 
     12 noconv = False
     13 if get_tk_patchlevel() < (8, 5, 11):
     14     noconv = str
     15 
     16 pixels_round = round
     17 if get_tk_patchlevel()[:3] == (8, 5, 11):
     18     # Issue #19085: Workaround a bug in Tk
     19     # http://core.tcl.tk/tk/info/3497848
     20     pixels_round = int
     21 
     22 
     23 _sentinel = object()
     24 
     25 class AbstractWidgetTest(AbstractTkTest):
     26     _conv_pixels = staticmethod(pixels_round)
     27     _conv_pad_pixels = None
     28     _stringify = False
     29 
     30     @property
     31     def scaling(self):
     32         try:
     33             return self._scaling
     34         except AttributeError:
     35             self._scaling = float(self.root.call('tk', 'scaling'))
     36             return self._scaling
     37 
     38     def _str(self, value):
     39         if not self._stringify and self.wantobjects and tcl_version >= (8, 6):
     40             return value
     41         if isinstance(value, tuple):
     42             return ' '.join(map(self._str, value))
     43         return str(value)
     44 
     45     def assertEqual2(self, actual, expected, msg=None, eq=object.__eq__):
     46         if eq(actual, expected):
     47             return
     48         self.assertEqual(actual, expected, msg)
     49 
     50     def checkParam(self, widget, name, value, *, expected=_sentinel,
     51                    conv=False, eq=None):
     52         widget[name] = value
     53         if expected is _sentinel:
     54             expected = value
     55         if conv:
     56             expected = conv(expected)
     57         if self._stringify or not self.wantobjects:
     58             if isinstance(expected, tuple):
     59                 expected = tkinter._join(expected)
     60             else:
     61                 expected = str(expected)
     62         if eq is None:
     63             eq = tcl_obj_eq
     64         self.assertEqual2(widget[name], expected, eq=eq)
     65         self.assertEqual2(widget.cget(name), expected, eq=eq)
     66         # XXX
     67         if not isinstance(widget, Scale):
     68             t = widget.configure(name)
     69             self.assertEqual(len(t), 5)
     70             self.assertEqual2(t[4], expected, eq=eq)
     71 
     72     def checkInvalidParam(self, widget, name, value, errmsg=None, *,
     73                           keep_orig=True):
     74         orig = widget[name]
     75         if errmsg is not None:
     76             errmsg = errmsg.format(value)
     77         with self.assertRaises(tkinter.TclError) as cm:
     78             widget[name] = value
     79         if errmsg is not None:
     80             self.assertEqual(str(cm.exception), errmsg)
     81         if keep_orig:
     82             self.assertEqual(widget[name], orig)
     83         else:
     84             widget[name] = orig
     85         with self.assertRaises(tkinter.TclError) as cm:
     86             widget.configure({name: value})
     87         if errmsg is not None:
     88             self.assertEqual(str(cm.exception), errmsg)
     89         if keep_orig:
     90             self.assertEqual(widget[name], orig)
     91         else:
     92             widget[name] = orig
     93 
     94     def checkParams(self, widget, name, *values, **kwargs):
     95         for value in values:
     96             self.checkParam(widget, name, value, **kwargs)
     97 
     98     def checkIntegerParam(self, widget, name, *values, **kwargs):
     99         self.checkParams(widget, name, *values, **kwargs)
    100         self.checkInvalidParam(widget, name, '',
    101                 errmsg='expected integer but got ""')
    102         self.checkInvalidParam(widget, name, '10p',
    103                 errmsg='expected integer but got "10p"')
    104         self.checkInvalidParam(widget, name, 3.2,
    105                 errmsg='expected integer but got "3.2"')
    106 
    107     def checkFloatParam(self, widget, name, *values, conv=float, **kwargs):
    108         for value in values:
    109             self.checkParam(widget, name, value, conv=conv, **kwargs)
    110         self.checkInvalidParam(widget, name, '',
    111                 errmsg='expected floating-point number but got ""')
    112         self.checkInvalidParam(widget, name, 'spam',
    113                 errmsg='expected floating-point number but got "spam"')
    114 
    115     def checkBooleanParam(self, widget, name):
    116         for value in (False, 0, 'false', 'no', 'off'):
    117             self.checkParam(widget, name, value, expected=0)
    118         for value in (True, 1, 'true', 'yes', 'on'):
    119             self.checkParam(widget, name, value, expected=1)
    120         self.checkInvalidParam(widget, name, '',
    121                 errmsg='expected boolean value but got ""')
    122         self.checkInvalidParam(widget, name, 'spam',
    123                 errmsg='expected boolean value but got "spam"')
    124 
    125     def checkColorParam(self, widget, name, *, allow_empty=None, **kwargs):
    126         self.checkParams(widget, name,
    127                          '#ff0000', '#00ff00', '#0000ff', '#123456',
    128                          'red', 'green', 'blue', 'white', 'black', 'grey',
    129                          **kwargs)
    130         self.checkInvalidParam(widget, name, 'spam',
    131                 errmsg='unknown color name "spam"')
    132 
    133     def checkCursorParam(self, widget, name, **kwargs):
    134         self.checkParams(widget, name, 'arrow', 'watch', 'cross', '',**kwargs)
    135         if tcl_version >= (8, 5):
    136             self.checkParam(widget, name, 'none')
    137         self.checkInvalidParam(widget, name, 'spam',
    138                 errmsg='bad cursor spec "spam"')
    139 
    140     def checkCommandParam(self, widget, name):
    141         def command(*args):
    142             pass
    143         widget[name] = command
    144         self.assertTrue(widget[name])
    145         self.checkParams(widget, name, '')
    146 
    147     def checkEnumParam(self, widget, name, *values, errmsg=None, **kwargs):
    148         self.checkParams(widget, name, *values, **kwargs)
    149         if errmsg is None:
    150             errmsg2 = ' %s "{}": must be %s%s or %s' % (
    151                     name,
    152                     ', '.join(values[:-1]),
    153                     ',' if len(values) > 2 else '',
    154                     values[-1])
    155             self.checkInvalidParam(widget, name, '',
    156                                    errmsg='ambiguous' + errmsg2)
    157             errmsg = 'bad' + errmsg2
    158         self.checkInvalidParam(widget, name, 'spam', errmsg=errmsg)
    159 
    160     def checkPixelsParam(self, widget, name, *values,
    161                          conv=None, keep_orig=True, **kwargs):
    162         if conv is None:
    163             conv = self._conv_pixels
    164         for value in values:
    165             expected = _sentinel
    166             conv1 = conv
    167             if isinstance(value, str):
    168                 if conv1 and conv1 is not str:
    169                     expected = pixels_conv(value) * self.scaling
    170                     conv1 = round
    171             self.checkParam(widget, name, value, expected=expected,
    172                             conv=conv1, **kwargs)
    173         self.checkInvalidParam(widget, name, '6x',
    174                 errmsg='bad screen distance "6x"', keep_orig=keep_orig)
    175         self.checkInvalidParam(widget, name, 'spam',
    176                 errmsg='bad screen distance "spam"', keep_orig=keep_orig)
    177 
    178     def checkReliefParam(self, widget, name):
    179         self.checkParams(widget, name,
    180                          'flat', 'groove', 'raised', 'ridge', 'solid', 'sunken')
    181         errmsg='bad relief "spam": must be '\
    182                'flat, groove, raised, ridge, solid, or sunken'
    183         if tcl_version < (8, 6):
    184             errmsg = None
    185         self.checkInvalidParam(widget, name, 'spam',
    186                 errmsg=errmsg)
    187 
    188     def checkImageParam(self, widget, name):
    189         image = tkinter.PhotoImage(master=self.root, name='image1')
    190         self.checkParam(widget, name, image, conv=str)
    191         self.checkInvalidParam(widget, name, 'spam',
    192                 errmsg='image "spam" doesn\'t exist')
    193         widget[name] = ''
    194 
    195     def checkVariableParam(self, widget, name, var):
    196         self.checkParam(widget, name, var, conv=str)
    197 
    198     def assertIsBoundingBox(self, bbox):
    199         self.assertIsNotNone(bbox)
    200         self.assertIsInstance(bbox, tuple)
    201         if len(bbox) != 4:
    202             self.fail('Invalid bounding box: %r' % (bbox,))
    203         for item in bbox:
    204             if not isinstance(item, int):
    205                 self.fail('Invalid bounding box: %r' % (bbox,))
    206                 break
    207 
    208 
    209     def test_keys(self):
    210         widget = self.create()
    211         keys = widget.keys()
    212         # XXX
    213         if not isinstance(widget, Scale):
    214             self.assertEqual(sorted(keys), sorted(widget.configure()))
    215         for k in keys:
    216             widget[k]
    217         # Test if OPTIONS contains all keys
    218         if test.support.verbose:
    219             aliases = {
    220                 'bd': 'borderwidth',
    221                 'bg': 'background',
    222                 'fg': 'foreground',
    223                 'invcmd': 'invalidcommand',
    224                 'vcmd': 'validatecommand',
    225             }
    226             keys = set(keys)
    227             expected = set(self.OPTIONS)
    228             for k in sorted(keys - expected):
    229                 if not (k in aliases and
    230                         aliases[k] in keys and
    231                         aliases[k] in expected):
    232                     print('%s.OPTIONS doesn\'t contain "%s"' %
    233                           (self.__class__.__name__, k))
    234 
    235 
    236 class StandardOptionsTests:
    237     STANDARD_OPTIONS = (
    238         'activebackground', 'activeborderwidth', 'activeforeground', 'anchor',
    239         'background', 'bitmap', 'borderwidth', 'compound', 'cursor',
    240         'disabledforeground', 'exportselection', 'font', 'foreground',
    241         'highlightbackground', 'highlightcolor', 'highlightthickness',
    242         'image', 'insertbackground', 'insertborderwidth',
    243         'insertofftime', 'insertontime', 'insertwidth',
    244         'jump', 'justify', 'orient', 'padx', 'pady', 'relief',
    245         'repeatdelay', 'repeatinterval',
    246         'selectbackground', 'selectborderwidth', 'selectforeground',
    247         'setgrid', 'takefocus', 'text', 'textvariable', 'troughcolor',
    248         'underline', 'wraplength', 'xscrollcommand', 'yscrollcommand',
    249     )
    250 
    251     def test_activebackground(self):
    252         widget = self.create()
    253         self.checkColorParam(widget, 'activebackground')
    254 
    255     def test_activeborderwidth(self):
    256         widget = self.create()
    257         self.checkPixelsParam(widget, 'activeborderwidth',
    258                               0, 1.3, 2.9, 6, -2, '10p')
    259 
    260     def test_activeforeground(self):
    261         widget = self.create()
    262         self.checkColorParam(widget, 'activeforeground')
    263 
    264     def test_anchor(self):
    265         widget = self.create()
    266         self.checkEnumParam(widget, 'anchor',
    267                 'n', 'ne', 'e', 'se', 's', 'sw', 'w', 'nw', 'center')
    268 
    269     def test_background(self):
    270         widget = self.create()
    271         self.checkColorParam(widget, 'background')
    272         if 'bg' in self.OPTIONS:
    273             self.checkColorParam(widget, 'bg')
    274 
    275     def test_bitmap(self):
    276         widget = self.create()
    277         self.checkParam(widget, 'bitmap', 'questhead')
    278         self.checkParam(widget, 'bitmap', 'gray50')
    279         filename = test.support.findfile('python.xbm', subdir='imghdrdata')
    280         self.checkParam(widget, 'bitmap', '@' + filename)
    281         # Cocoa Tk widgets don't detect invalid -bitmap values
    282         # See https://core.tcl.tk/tk/info/31cd33dbf0
    283         if not ('aqua' in self.root.tk.call('tk', 'windowingsystem') and
    284                 'AppKit' in self.root.winfo_server()):
    285             self.checkInvalidParam(widget, 'bitmap', 'spam',
    286                     errmsg='bitmap "spam" not defined')
    287 
    288     def test_borderwidth(self):
    289         widget = self.create()
    290         self.checkPixelsParam(widget, 'borderwidth',
    291                               0, 1.3, 2.6, 6, -2, '10p')
    292         if 'bd' in self.OPTIONS:
    293             self.checkPixelsParam(widget, 'bd', 0, 1.3, 2.6, 6, -2, '10p')
    294 
    295     def test_compound(self):
    296         widget = self.create()
    297         self.checkEnumParam(widget, 'compound',
    298                 'bottom', 'center', 'left', 'none', 'right', 'top')
    299 
    300     def test_cursor(self):
    301         widget = self.create()
    302         self.checkCursorParam(widget, 'cursor')
    303 
    304     def test_disabledforeground(self):
    305         widget = self.create()
    306         self.checkColorParam(widget, 'disabledforeground')
    307 
    308     def test_exportselection(self):
    309         widget = self.create()
    310         self.checkBooleanParam(widget, 'exportselection')
    311 
    312     def test_font(self):
    313         widget = self.create()
    314         self.checkParam(widget, 'font',
    315                         '-Adobe-Helvetica-Medium-R-Normal--*-120-*-*-*-*-*-*')
    316         self.checkInvalidParam(widget, 'font', '',
    317                                errmsg='font "" doesn\'t exist')
    318 
    319     def test_foreground(self):
    320         widget = self.create()
    321         self.checkColorParam(widget, 'foreground')
    322         if 'fg' in self.OPTIONS:
    323             self.checkColorParam(widget, 'fg')
    324 
    325     def test_highlightbackground(self):
    326         widget = self.create()
    327         self.checkColorParam(widget, 'highlightbackground')
    328 
    329     def test_highlightcolor(self):
    330         widget = self.create()
    331         self.checkColorParam(widget, 'highlightcolor')
    332 
    333     def test_highlightthickness(self):
    334         widget = self.create()
    335         self.checkPixelsParam(widget, 'highlightthickness',
    336                               0, 1.3, 2.6, 6, '10p')
    337         self.checkParam(widget, 'highlightthickness', -2, expected=0,
    338                         conv=self._conv_pixels)
    339 
    340     @unittest.skipIf(sys.platform == 'darwin',
    341                      'crashes with Cocoa Tk (issue19733)')
    342     def test_image(self):
    343         widget = self.create()
    344         self.checkImageParam(widget, 'image')
    345 
    346     def test_insertbackground(self):
    347         widget = self.create()
    348         self.checkColorParam(widget, 'insertbackground')
    349 
    350     def test_insertborderwidth(self):
    351         widget = self.create()
    352         self.checkPixelsParam(widget, 'insertborderwidth',
    353                               0, 1.3, 2.6, 6, -2, '10p')
    354 
    355     def test_insertofftime(self):
    356         widget = self.create()
    357         self.checkIntegerParam(widget, 'insertofftime', 100)
    358 
    359     def test_insertontime(self):
    360         widget = self.create()
    361         self.checkIntegerParam(widget, 'insertontime', 100)
    362 
    363     def test_insertwidth(self):
    364         widget = self.create()
    365         self.checkPixelsParam(widget, 'insertwidth', 1.3, 2.6, -2, '10p')
    366 
    367     def test_jump(self):
    368         widget = self.create()
    369         self.checkBooleanParam(widget, 'jump')
    370 
    371     def test_justify(self):
    372         widget = self.create()
    373         self.checkEnumParam(widget, 'justify', 'left', 'right', 'center',
    374                 errmsg='bad justification "{}": must be '
    375                        'left, right, or center')
    376         self.checkInvalidParam(widget, 'justify', '',
    377                 errmsg='ambiguous justification "": must be '
    378                        'left, right, or center')
    379 
    380     def test_orient(self):
    381         widget = self.create()
    382         self.assertEqual(str(widget['orient']), self.default_orient)
    383         self.checkEnumParam(widget, 'orient', 'horizontal', 'vertical')
    384 
    385     def test_padx(self):
    386         widget = self.create()
    387         self.checkPixelsParam(widget, 'padx', 3, 4.4, 5.6, -2, '12m',
    388                               conv=self._conv_pad_pixels)
    389 
    390     def test_pady(self):
    391         widget = self.create()
    392         self.checkPixelsParam(widget, 'pady', 3, 4.4, 5.6, -2, '12m',
    393                               conv=self._conv_pad_pixels)
    394 
    395     def test_relief(self):
    396         widget = self.create()
    397         self.checkReliefParam(widget, 'relief')
    398 
    399     def test_repeatdelay(self):
    400         widget = self.create()
    401         self.checkIntegerParam(widget, 'repeatdelay', -500, 500)
    402 
    403     def test_repeatinterval(self):
    404         widget = self.create()
    405         self.checkIntegerParam(widget, 'repeatinterval', -500, 500)
    406 
    407     def test_selectbackground(self):
    408         widget = self.create()
    409         self.checkColorParam(widget, 'selectbackground')
    410 
    411     def test_selectborderwidth(self):
    412         widget = self.create()
    413         self.checkPixelsParam(widget, 'selectborderwidth', 1.3, 2.6, -2, '10p')
    414 
    415     def test_selectforeground(self):
    416         widget = self.create()
    417         self.checkColorParam(widget, 'selectforeground')
    418 
    419     def test_setgrid(self):
    420         widget = self.create()
    421         self.checkBooleanParam(widget, 'setgrid')
    422 
    423     def test_state(self):
    424         widget = self.create()
    425         self.checkEnumParam(widget, 'state', 'active', 'disabled', 'normal')
    426 
    427     def test_takefocus(self):
    428         widget = self.create()
    429         self.checkParams(widget, 'takefocus', '0', '1', '')
    430 
    431     def test_text(self):
    432         widget = self.create()
    433         self.checkParams(widget, 'text', '', 'any string')
    434 
    435     def test_textvariable(self):
    436         widget = self.create()
    437         var = tkinter.StringVar(self.root)
    438         self.checkVariableParam(widget, 'textvariable', var)
    439 
    440     def test_troughcolor(self):
    441         widget = self.create()
    442         self.checkColorParam(widget, 'troughcolor')
    443 
    444     def test_underline(self):
    445         widget = self.create()
    446         self.checkIntegerParam(widget, 'underline', 0, 1, 10)
    447 
    448     def test_wraplength(self):
    449         widget = self.create()
    450         self.checkPixelsParam(widget, 'wraplength', 100)
    451 
    452     def test_xscrollcommand(self):
    453         widget = self.create()
    454         self.checkCommandParam(widget, 'xscrollcommand')
    455 
    456     def test_yscrollcommand(self):
    457         widget = self.create()
    458         self.checkCommandParam(widget, 'yscrollcommand')
    459 
    460     # non-standard but common options
    461 
    462     def test_command(self):
    463         widget = self.create()
    464         self.checkCommandParam(widget, 'command')
    465 
    466     def test_indicatoron(self):
    467         widget = self.create()
    468         self.checkBooleanParam(widget, 'indicatoron')
    469 
    470     def test_offrelief(self):
    471         widget = self.create()
    472         self.checkReliefParam(widget, 'offrelief')
    473 
    474     def test_overrelief(self):
    475         widget = self.create()
    476         self.checkReliefParam(widget, 'overrelief')
    477 
    478     def test_selectcolor(self):
    479         widget = self.create()
    480         self.checkColorParam(widget, 'selectcolor')
    481 
    482     def test_selectimage(self):
    483         widget = self.create()
    484         self.checkImageParam(widget, 'selectimage')
    485 
    486     @requires_tcl(8, 5)
    487     def test_tristateimage(self):
    488         widget = self.create()
    489         self.checkImageParam(widget, 'tristateimage')
    490 
    491     @requires_tcl(8, 5)
    492     def test_tristatevalue(self):
    493         widget = self.create()
    494         self.checkParam(widget, 'tristatevalue', 'unknowable')
    495 
    496     def test_variable(self):
    497         widget = self.create()
    498         var = tkinter.DoubleVar(self.root)
    499         self.checkVariableParam(widget, 'variable', var)
    500 
    501 
    502 class IntegerSizeTests:
    503     def test_height(self):
    504         widget = self.create()
    505         self.checkIntegerParam(widget, 'height', 100, -100, 0)
    506 
    507     def test_width(self):
    508         widget = self.create()
    509         self.checkIntegerParam(widget, 'width', 402, -402, 0)
    510 
    511 
    512 class PixelSizeTests:
    513     def test_height(self):
    514         widget = self.create()
    515         self.checkPixelsParam(widget, 'height', 100, 101.2, 102.6, -100, 0, '3c')
    516 
    517     def test_width(self):
    518         widget = self.create()
    519         self.checkPixelsParam(widget, 'width', 402, 403.4, 404.6, -402, 0, '5i')
    520 
    521 
    522 def add_standard_options(*source_classes):
    523     # This decorator adds test_xxx methods from source classes for every xxx
    524     # option in the OPTIONS class attribute if they are not defined explicitly.
    525     def decorator(cls):
    526         for option in cls.OPTIONS:
    527             methodname = 'test_' + option
    528             if not hasattr(cls, methodname):
    529                 for source_class in source_classes:
    530                     if hasattr(source_class, methodname):
    531                         setattr(cls, methodname,
    532                                 getattr(source_class, methodname))
    533                         break
    534                 else:
    535                     def test(self, option=option):
    536                         widget = self.create()
    537                         widget[option]
    538                         raise AssertionError('Option "%s" is not tested in %s' %
    539                                              (option, cls.__name__))
    540                     test.__name__ = methodname
    541                     setattr(cls, methodname, test)
    542         return cls
    543     return decorator
    544 
    545 def setUpModule():
    546     if test.support.verbose:
    547         tcl = tkinter.Tcl()
    548         print('patchlevel =', tcl.call('info', 'patchlevel'))
    549