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