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