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