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