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