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