widget_tests.py 19 KB

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