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