widget_tests.py 19 KB

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