• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1import unittest
2import tkinter
3from tkinter import ttk, TclError
4from test.support import requires
5import sys
6
7from tkinter.test.test_ttk.test_functions import MockTclObj
8from tkinter.test.support import (AbstractTkTest, tcl_version, get_tk_patchlevel,
9                                  simulate_mouse_click)
10from tkinter.test.widget_tests import (add_standard_options, noconv,
11    AbstractWidgetTest, StandardOptionsTests, IntegerSizeTests, PixelSizeTests,
12    setUpModule)
13
14requires('gui')
15
16
17class StandardTtkOptionsTests(StandardOptionsTests):
18
19    def test_class(self):
20        widget = self.create()
21        self.assertEqual(widget['class'], '')
22        errmsg='attempt to change read-only option'
23        if get_tk_patchlevel() < (8, 6, 0, 'beta', 3):
24            errmsg='Attempt to change read-only option'
25        self.checkInvalidParam(widget, 'class', 'Foo', errmsg=errmsg)
26        widget2 = self.create(class_='Foo')
27        self.assertEqual(widget2['class'], 'Foo')
28
29    def test_padding(self):
30        widget = self.create()
31        self.checkParam(widget, 'padding', 0, expected=('0',))
32        self.checkParam(widget, 'padding', 5, expected=('5',))
33        self.checkParam(widget, 'padding', (5, 6), expected=('5', '6'))
34        self.checkParam(widget, 'padding', (5, 6, 7),
35                        expected=('5', '6', '7'))
36        self.checkParam(widget, 'padding', (5, 6, 7, 8),
37                        expected=('5', '6', '7', '8'))
38        self.checkParam(widget, 'padding', ('5p', '6p', '7p', '8p'))
39        self.checkParam(widget, 'padding', (), expected='')
40
41    def test_style(self):
42        widget = self.create()
43        self.assertEqual(widget['style'], '')
44        errmsg = 'Layout Foo not found'
45        if hasattr(self, 'default_orient'):
46            errmsg = ('Layout %s.Foo not found' %
47                      getattr(self, 'default_orient').title())
48        self.checkInvalidParam(widget, 'style', 'Foo',
49                errmsg=errmsg)
50        widget2 = self.create(class_='Foo')
51        self.assertEqual(widget2['class'], 'Foo')
52        # XXX
53        pass
54
55
56class WidgetTest(AbstractTkTest, unittest.TestCase):
57    """Tests methods available in every ttk widget."""
58
59    def setUp(self):
60        super().setUp()
61        self.widget = ttk.Button(self.root, width=0, text="Text")
62        self.widget.pack()
63
64
65    def test_identify(self):
66        self.widget.update()
67        self.assertEqual(self.widget.identify(
68            int(self.widget.winfo_width() / 2),
69            int(self.widget.winfo_height() / 2)
70            ), "label")
71        self.assertEqual(self.widget.identify(-1, -1), "")
72
73        self.assertRaises(tkinter.TclError, self.widget.identify, None, 5)
74        self.assertRaises(tkinter.TclError, self.widget.identify, 5, None)
75        self.assertRaises(tkinter.TclError, self.widget.identify, 5, '')
76
77
78    def test_widget_state(self):
79        # XXX not sure about the portability of all these tests
80        self.assertEqual(self.widget.state(), ())
81        self.assertEqual(self.widget.instate(['!disabled']), True)
82
83        # changing from !disabled to disabled
84        self.assertEqual(self.widget.state(['disabled']), ('!disabled', ))
85        # no state change
86        self.assertEqual(self.widget.state(['disabled']), ())
87        # change back to !disable but also active
88        self.assertEqual(self.widget.state(['!disabled', 'active']),
89            ('!active', 'disabled'))
90        # no state changes, again
91        self.assertEqual(self.widget.state(['!disabled', 'active']), ())
92        self.assertEqual(self.widget.state(['active', '!disabled']), ())
93
94        def test_cb(arg1, **kw):
95            return arg1, kw
96        self.assertEqual(self.widget.instate(['!disabled'],
97            test_cb, "hi", **{"msg": "there"}),
98            ('hi', {'msg': 'there'}))
99
100        # attempt to set invalid statespec
101        currstate = self.widget.state()
102        self.assertRaises(tkinter.TclError, self.widget.instate,
103            ['badstate'])
104        self.assertRaises(tkinter.TclError, self.widget.instate,
105            ['disabled', 'badstate'])
106        # verify that widget didn't change its state
107        self.assertEqual(currstate, self.widget.state())
108
109        # ensuring that passing None as state doesn't modify current state
110        self.widget.state(['active', '!disabled'])
111        self.assertEqual(self.widget.state(), ('active', ))
112
113
114class AbstractToplevelTest(AbstractWidgetTest, PixelSizeTests):
115    _conv_pixels = noconv
116
117
118@add_standard_options(StandardTtkOptionsTests)
119class FrameTest(AbstractToplevelTest, unittest.TestCase):
120    OPTIONS = (
121        'borderwidth', 'class', 'cursor', 'height',
122        'padding', 'relief', 'style', 'takefocus',
123        'width',
124    )
125
126    def create(self, **kwargs):
127        return ttk.Frame(self.root, **kwargs)
128
129
130@add_standard_options(StandardTtkOptionsTests)
131class LabelFrameTest(AbstractToplevelTest, unittest.TestCase):
132    OPTIONS = (
133        'borderwidth', 'class', 'cursor', 'height',
134        'labelanchor', 'labelwidget',
135        'padding', 'relief', 'style', 'takefocus',
136        'text', 'underline', 'width',
137    )
138
139    def create(self, **kwargs):
140        return ttk.LabelFrame(self.root, **kwargs)
141
142    def test_labelanchor(self):
143        widget = self.create()
144        self.checkEnumParam(widget, 'labelanchor',
145                'e', 'en', 'es', 'n', 'ne', 'nw', 's', 'se', 'sw', 'w', 'wn', 'ws',
146                errmsg='Bad label anchor specification {}')
147        self.checkInvalidParam(widget, 'labelanchor', 'center')
148
149    def test_labelwidget(self):
150        widget = self.create()
151        label = ttk.Label(self.root, text='Mupp', name='foo')
152        self.checkParam(widget, 'labelwidget', label, expected='.foo')
153        label.destroy()
154
155
156class AbstractLabelTest(AbstractWidgetTest):
157
158    def checkImageParam(self, widget, name):
159        image = tkinter.PhotoImage(master=self.root, name='image1')
160        image2 = tkinter.PhotoImage(master=self.root, name='image2')
161        self.checkParam(widget, name, image, expected=('image1',))
162        self.checkParam(widget, name, 'image1', expected=('image1',))
163        self.checkParam(widget, name, (image,), expected=('image1',))
164        self.checkParam(widget, name, (image, 'active', image2),
165                        expected=('image1', 'active', 'image2'))
166        self.checkParam(widget, name, 'image1 active image2',
167                        expected=('image1', 'active', 'image2'))
168        self.checkInvalidParam(widget, name, 'spam',
169                errmsg='image "spam" doesn\'t exist')
170
171    def test_compound(self):
172        widget = self.create()
173        self.checkEnumParam(widget, 'compound',
174                'none', 'text', 'image', 'center',
175                'top', 'bottom', 'left', 'right')
176
177    def test_state(self):
178        widget = self.create()
179        self.checkParams(widget, 'state', 'active', 'disabled', 'normal')
180
181    def test_width(self):
182        widget = self.create()
183        self.checkParams(widget, 'width', 402, -402, 0)
184
185
186@add_standard_options(StandardTtkOptionsTests)
187class LabelTest(AbstractLabelTest, unittest.TestCase):
188    OPTIONS = (
189        'anchor', 'background', 'borderwidth',
190        'class', 'compound', 'cursor', 'font', 'foreground',
191        'image', 'justify', 'padding', 'relief', 'state', 'style',
192        'takefocus', 'text', 'textvariable',
193        'underline', 'width', 'wraplength',
194    )
195    _conv_pixels = noconv
196
197    def create(self, **kwargs):
198        return ttk.Label(self.root, **kwargs)
199
200    def test_font(self):
201        widget = self.create()
202        self.checkParam(widget, 'font',
203                        '-Adobe-Helvetica-Medium-R-Normal--*-120-*-*-*-*-*-*')
204
205
206@add_standard_options(StandardTtkOptionsTests)
207class ButtonTest(AbstractLabelTest, unittest.TestCase):
208    OPTIONS = (
209        'class', 'command', 'compound', 'cursor', 'default',
210        'image', 'padding', 'state', 'style',
211        'takefocus', 'text', 'textvariable',
212        'underline', 'width',
213    )
214
215    def create(self, **kwargs):
216        return ttk.Button(self.root, **kwargs)
217
218    def test_default(self):
219        widget = self.create()
220        self.checkEnumParam(widget, 'default', 'normal', 'active', 'disabled')
221
222    def test_invoke(self):
223        success = []
224        btn = ttk.Button(self.root, command=lambda: success.append(1))
225        btn.invoke()
226        self.assertTrue(success)
227
228
229@add_standard_options(StandardTtkOptionsTests)
230class CheckbuttonTest(AbstractLabelTest, unittest.TestCase):
231    OPTIONS = (
232        'class', 'command', 'compound', 'cursor',
233        'image',
234        'offvalue', 'onvalue',
235        'padding', 'state', 'style',
236        'takefocus', 'text', 'textvariable',
237        'underline', 'variable', 'width',
238    )
239
240    def create(self, **kwargs):
241        return ttk.Checkbutton(self.root, **kwargs)
242
243    def test_offvalue(self):
244        widget = self.create()
245        self.checkParams(widget, 'offvalue', 1, 2.3, '', 'any string')
246
247    def test_onvalue(self):
248        widget = self.create()
249        self.checkParams(widget, 'onvalue', 1, 2.3, '', 'any string')
250
251    def test_invoke(self):
252        success = []
253        def cb_test():
254            success.append(1)
255            return "cb test called"
256
257        cbtn = ttk.Checkbutton(self.root, command=cb_test)
258        # the variable automatically created by ttk.Checkbutton is actually
259        # undefined till we invoke the Checkbutton
260        self.assertEqual(cbtn.state(), ('alternate', ))
261        self.assertRaises(tkinter.TclError, cbtn.tk.globalgetvar,
262            cbtn['variable'])
263
264        res = cbtn.invoke()
265        self.assertEqual(res, "cb test called")
266        self.assertEqual(cbtn['onvalue'],
267            cbtn.tk.globalgetvar(cbtn['variable']))
268        self.assertTrue(success)
269
270        cbtn['command'] = ''
271        res = cbtn.invoke()
272        self.assertFalse(str(res))
273        self.assertLessEqual(len(success), 1)
274        self.assertEqual(cbtn['offvalue'],
275            cbtn.tk.globalgetvar(cbtn['variable']))
276
277
278@add_standard_options(IntegerSizeTests, StandardTtkOptionsTests)
279class EntryTest(AbstractWidgetTest, unittest.TestCase):
280    OPTIONS = (
281        'background', 'class', 'cursor',
282        'exportselection', 'font', 'foreground',
283        'invalidcommand', 'justify',
284        'show', 'state', 'style', 'takefocus', 'textvariable',
285        'validate', 'validatecommand', 'width', 'xscrollcommand',
286    )
287
288    def setUp(self):
289        super().setUp()
290        self.entry = self.create()
291
292    def create(self, **kwargs):
293        return ttk.Entry(self.root, **kwargs)
294
295    def test_invalidcommand(self):
296        widget = self.create()
297        self.checkCommandParam(widget, 'invalidcommand')
298
299    def test_show(self):
300        widget = self.create()
301        self.checkParam(widget, 'show', '*')
302        self.checkParam(widget, 'show', '')
303        self.checkParam(widget, 'show', ' ')
304
305    def test_state(self):
306        widget = self.create()
307        self.checkParams(widget, 'state',
308                         'disabled', 'normal', 'readonly')
309
310    def test_validate(self):
311        widget = self.create()
312        self.checkEnumParam(widget, 'validate',
313                'all', 'key', 'focus', 'focusin', 'focusout', 'none')
314
315    def test_validatecommand(self):
316        widget = self.create()
317        self.checkCommandParam(widget, 'validatecommand')
318
319
320    def test_bbox(self):
321        self.assertIsBoundingBox(self.entry.bbox(0))
322        self.assertRaises(tkinter.TclError, self.entry.bbox, 'noindex')
323        self.assertRaises(tkinter.TclError, self.entry.bbox, None)
324
325
326    def test_identify(self):
327        self.entry.pack()
328        self.entry.update()
329
330        # bpo-27313: macOS Cocoa widget differs from X, allow either
331        if sys.platform == 'darwin':
332            self.assertIn(self.entry.identify(5, 5),
333                ("textarea", "Combobox.button") )
334        else:
335            self.assertEqual(self.entry.identify(5, 5), "textarea")
336        self.assertEqual(self.entry.identify(-1, -1), "")
337
338        self.assertRaises(tkinter.TclError, self.entry.identify, None, 5)
339        self.assertRaises(tkinter.TclError, self.entry.identify, 5, None)
340        self.assertRaises(tkinter.TclError, self.entry.identify, 5, '')
341
342
343    def test_validation_options(self):
344        success = []
345        test_invalid = lambda: success.append(True)
346
347        self.entry['validate'] = 'none'
348        self.entry['validatecommand'] = lambda: False
349
350        self.entry['invalidcommand'] = test_invalid
351        self.entry.validate()
352        self.assertTrue(success)
353
354        self.entry['invalidcommand'] = ''
355        self.entry.validate()
356        self.assertEqual(len(success), 1)
357
358        self.entry['invalidcommand'] = test_invalid
359        self.entry['validatecommand'] = lambda: True
360        self.entry.validate()
361        self.assertEqual(len(success), 1)
362
363        self.entry['validatecommand'] = ''
364        self.entry.validate()
365        self.assertEqual(len(success), 1)
366
367        self.entry['validatecommand'] = True
368        self.assertRaises(tkinter.TclError, self.entry.validate)
369
370
371    def test_validation(self):
372        validation = []
373        def validate(to_insert):
374            if not 'a' <= to_insert.lower() <= 'z':
375                validation.append(False)
376                return False
377            validation.append(True)
378            return True
379
380        self.entry['validate'] = 'key'
381        self.entry['validatecommand'] = self.entry.register(validate), '%S'
382
383        self.entry.insert('end', 1)
384        self.entry.insert('end', 'a')
385        self.assertEqual(validation, [False, True])
386        self.assertEqual(self.entry.get(), 'a')
387
388
389    def test_revalidation(self):
390        def validate(content):
391            for letter in content:
392                if not 'a' <= letter.lower() <= 'z':
393                    return False
394            return True
395
396        self.entry['validatecommand'] = self.entry.register(validate), '%P'
397
398        self.entry.insert('end', 'avocado')
399        self.assertEqual(self.entry.validate(), True)
400        self.assertEqual(self.entry.state(), ())
401
402        self.entry.delete(0, 'end')
403        self.assertEqual(self.entry.get(), '')
404
405        self.entry.insert('end', 'a1b')
406        self.assertEqual(self.entry.validate(), False)
407        self.assertEqual(self.entry.state(), ('invalid', ))
408
409        self.entry.delete(1)
410        self.assertEqual(self.entry.validate(), True)
411        self.assertEqual(self.entry.state(), ())
412
413
414@add_standard_options(IntegerSizeTests, StandardTtkOptionsTests)
415class ComboboxTest(EntryTest, unittest.TestCase):
416    OPTIONS = (
417        'background', 'class', 'cursor', 'exportselection',
418        'font', 'foreground', 'height', 'invalidcommand',
419        'justify', 'postcommand', 'show', 'state', 'style',
420        'takefocus', 'textvariable',
421        'validate', 'validatecommand', 'values',
422        'width', 'xscrollcommand',
423    )
424
425    def setUp(self):
426        super().setUp()
427        self.combo = self.create()
428
429    def create(self, **kwargs):
430        return ttk.Combobox(self.root, **kwargs)
431
432    def test_height(self):
433        widget = self.create()
434        self.checkParams(widget, 'height', 100, 101.2, 102.6, -100, 0, '1i')
435
436    def _show_drop_down_listbox(self):
437        width = self.combo.winfo_width()
438        x, y = width - 5, 5
439        self.assertRegex(self.combo.identify(x, y), r'.*downarrow\Z')
440        self.combo.event_generate('<ButtonPress-1>', x=x, y=y)
441        self.combo.event_generate('<ButtonRelease-1>', x=x, y=y)
442        self.combo.update_idletasks()
443
444    def test_virtual_event(self):
445        success = []
446
447        self.combo['values'] = [1]
448        self.combo.bind('<<ComboboxSelected>>',
449            lambda evt: success.append(True))
450        self.combo.pack()
451        self.combo.update()
452
453        height = self.combo.winfo_height()
454        self._show_drop_down_listbox()
455        self.combo.update()
456        self.combo.event_generate('<Return>')
457        self.combo.update()
458
459        self.assertTrue(success)
460
461
462    def test_postcommand(self):
463        success = []
464
465        self.combo['postcommand'] = lambda: success.append(True)
466        self.combo.pack()
467        self.combo.update()
468
469        self._show_drop_down_listbox()
470        self.assertTrue(success)
471
472        # testing postcommand removal
473        self.combo['postcommand'] = ''
474        self._show_drop_down_listbox()
475        self.assertEqual(len(success), 1)
476
477
478    def test_values(self):
479        def check_get_current(getval, currval):
480            self.assertEqual(self.combo.get(), getval)
481            self.assertEqual(self.combo.current(), currval)
482
483        self.assertEqual(self.combo['values'],
484                         () if tcl_version < (8, 5) else '')
485        check_get_current('', -1)
486
487        self.checkParam(self.combo, 'values', 'mon tue wed thur',
488                        expected=('mon', 'tue', 'wed', 'thur'))
489        self.checkParam(self.combo, 'values', ('mon', 'tue', 'wed', 'thur'))
490        self.checkParam(self.combo, 'values', (42, 3.14, '', 'any string'))
491        self.checkParam(self.combo, 'values', '')
492
493        self.combo['values'] = ['a', 1, 'c']
494
495        self.combo.set('c')
496        check_get_current('c', 2)
497
498        self.combo.current(0)
499        check_get_current('a', 0)
500
501        self.combo.set('d')
502        check_get_current('d', -1)
503
504        # testing values with empty string
505        self.combo.set('')
506        self.combo['values'] = (1, 2, '', 3)
507        check_get_current('', 2)
508
509        # testing values with empty string set through configure
510        self.combo.configure(values=[1, '', 2])
511        self.assertEqual(self.combo['values'],
512                         ('1', '', '2') if self.wantobjects else
513                         '1 {} 2')
514
515        # testing values with spaces
516        self.combo['values'] = ['a b', 'a\tb', 'a\nb']
517        self.assertEqual(self.combo['values'],
518                         ('a b', 'a\tb', 'a\nb') if self.wantobjects else
519                         '{a b} {a\tb} {a\nb}')
520
521        # testing values with special characters
522        self.combo['values'] = [r'a\tb', '"a"', '} {']
523        self.assertEqual(self.combo['values'],
524                         (r'a\tb', '"a"', '} {') if self.wantobjects else
525                         r'a\\tb {"a"} \}\ \{')
526
527        # out of range
528        self.assertRaises(tkinter.TclError, self.combo.current,
529            len(self.combo['values']))
530        # it expects an integer (or something that can be converted to int)
531        self.assertRaises(tkinter.TclError, self.combo.current, '')
532
533        # testing creating combobox with empty string in values
534        combo2 = ttk.Combobox(self.root, values=[1, 2, ''])
535        self.assertEqual(combo2['values'],
536                         ('1', '2', '') if self.wantobjects else '1 2 {}')
537        combo2.destroy()
538
539
540@add_standard_options(IntegerSizeTests, StandardTtkOptionsTests)
541class PanedWindowTest(AbstractWidgetTest, unittest.TestCase):
542    OPTIONS = (
543        'class', 'cursor', 'height',
544        'orient', 'style', 'takefocus', 'width',
545    )
546
547    def setUp(self):
548        super().setUp()
549        self.paned = self.create()
550
551    def create(self, **kwargs):
552        return ttk.PanedWindow(self.root, **kwargs)
553
554    def test_orient(self):
555        widget = self.create()
556        self.assertEqual(str(widget['orient']), 'vertical')
557        errmsg='attempt to change read-only option'
558        if get_tk_patchlevel() < (8, 6, 0, 'beta', 3):
559            errmsg='Attempt to change read-only option'
560        self.checkInvalidParam(widget, 'orient', 'horizontal',
561                errmsg=errmsg)
562        widget2 = self.create(orient='horizontal')
563        self.assertEqual(str(widget2['orient']), 'horizontal')
564
565    def test_add(self):
566        # attempt to add a child that is not a direct child of the paned window
567        label = ttk.Label(self.paned)
568        child = ttk.Label(label)
569        self.assertRaises(tkinter.TclError, self.paned.add, child)
570        label.destroy()
571        child.destroy()
572        # another attempt
573        label = ttk.Label(self.root)
574        child = ttk.Label(label)
575        self.assertRaises(tkinter.TclError, self.paned.add, child)
576        child.destroy()
577        label.destroy()
578
579        good_child = ttk.Label(self.root)
580        self.paned.add(good_child)
581        # re-adding a child is not accepted
582        self.assertRaises(tkinter.TclError, self.paned.add, good_child)
583
584        other_child = ttk.Label(self.paned)
585        self.paned.add(other_child)
586        self.assertEqual(self.paned.pane(0), self.paned.pane(1))
587        self.assertRaises(tkinter.TclError, self.paned.pane, 2)
588        good_child.destroy()
589        other_child.destroy()
590        self.assertRaises(tkinter.TclError, self.paned.pane, 0)
591
592
593    def test_forget(self):
594        self.assertRaises(tkinter.TclError, self.paned.forget, None)
595        self.assertRaises(tkinter.TclError, self.paned.forget, 0)
596
597        self.paned.add(ttk.Label(self.root))
598        self.paned.forget(0)
599        self.assertRaises(tkinter.TclError, self.paned.forget, 0)
600
601
602    def test_insert(self):
603        self.assertRaises(tkinter.TclError, self.paned.insert, None, 0)
604        self.assertRaises(tkinter.TclError, self.paned.insert, 0, None)
605        self.assertRaises(tkinter.TclError, self.paned.insert, 0, 0)
606
607        child = ttk.Label(self.root)
608        child2 = ttk.Label(self.root)
609        child3 = ttk.Label(self.root)
610
611        self.assertRaises(tkinter.TclError, self.paned.insert, 0, child)
612
613        self.paned.insert('end', child2)
614        self.paned.insert(0, child)
615        self.assertEqual(self.paned.panes(), (str(child), str(child2)))
616
617        self.paned.insert(0, child2)
618        self.assertEqual(self.paned.panes(), (str(child2), str(child)))
619
620        self.paned.insert('end', child3)
621        self.assertEqual(self.paned.panes(),
622            (str(child2), str(child), str(child3)))
623
624        # reinserting a child should move it to its current position
625        panes = self.paned.panes()
626        self.paned.insert('end', child3)
627        self.assertEqual(panes, self.paned.panes())
628
629        # moving child3 to child2 position should result in child2 ending up
630        # in previous child position and child ending up in previous child3
631        # position
632        self.paned.insert(child2, child3)
633        self.assertEqual(self.paned.panes(),
634            (str(child3), str(child2), str(child)))
635
636
637    def test_pane(self):
638        self.assertRaises(tkinter.TclError, self.paned.pane, 0)
639
640        child = ttk.Label(self.root)
641        self.paned.add(child)
642        self.assertIsInstance(self.paned.pane(0), dict)
643        self.assertEqual(self.paned.pane(0, weight=None),
644                         0 if self.wantobjects else '0')
645        # newer form for querying a single option
646        self.assertEqual(self.paned.pane(0, 'weight'),
647                         0 if self.wantobjects else '0')
648        self.assertEqual(self.paned.pane(0), self.paned.pane(str(child)))
649
650        self.assertRaises(tkinter.TclError, self.paned.pane, 0,
651            badoption='somevalue')
652
653
654    def test_sashpos(self):
655        self.assertRaises(tkinter.TclError, self.paned.sashpos, None)
656        self.assertRaises(tkinter.TclError, self.paned.sashpos, '')
657        self.assertRaises(tkinter.TclError, self.paned.sashpos, 0)
658
659        child = ttk.Label(self.paned, text='a')
660        self.paned.add(child, weight=1)
661        self.assertRaises(tkinter.TclError, self.paned.sashpos, 0)
662        child2 = ttk.Label(self.paned, text='b')
663        self.paned.add(child2)
664        self.assertRaises(tkinter.TclError, self.paned.sashpos, 1)
665
666        self.paned.pack(expand=True, fill='both')
667
668        curr_pos = self.paned.sashpos(0)
669        self.paned.sashpos(0, 1000)
670        self.assertNotEqual(curr_pos, self.paned.sashpos(0))
671        self.assertIsInstance(self.paned.sashpos(0), int)
672
673
674@add_standard_options(StandardTtkOptionsTests)
675class RadiobuttonTest(AbstractLabelTest, unittest.TestCase):
676    OPTIONS = (
677        'class', 'command', 'compound', 'cursor',
678        'image',
679        'padding', 'state', 'style',
680        'takefocus', 'text', 'textvariable',
681        'underline', 'value', 'variable', 'width',
682    )
683
684    def create(self, **kwargs):
685        return ttk.Radiobutton(self.root, **kwargs)
686
687    def test_value(self):
688        widget = self.create()
689        self.checkParams(widget, 'value', 1, 2.3, '', 'any string')
690
691    def test_invoke(self):
692        success = []
693        def cb_test():
694            success.append(1)
695            return "cb test called"
696
697        myvar = tkinter.IntVar(self.root)
698        cbtn = ttk.Radiobutton(self.root, command=cb_test,
699                               variable=myvar, value=0)
700        cbtn2 = ttk.Radiobutton(self.root, command=cb_test,
701                                variable=myvar, value=1)
702
703        if self.wantobjects:
704            conv = lambda x: x
705        else:
706            conv = int
707
708        res = cbtn.invoke()
709        self.assertEqual(res, "cb test called")
710        self.assertEqual(conv(cbtn['value']), myvar.get())
711        self.assertEqual(myvar.get(),
712            conv(cbtn.tk.globalgetvar(cbtn['variable'])))
713        self.assertTrue(success)
714
715        cbtn2['command'] = ''
716        res = cbtn2.invoke()
717        self.assertEqual(str(res), '')
718        self.assertLessEqual(len(success), 1)
719        self.assertEqual(conv(cbtn2['value']), myvar.get())
720        self.assertEqual(myvar.get(),
721            conv(cbtn.tk.globalgetvar(cbtn['variable'])))
722
723        self.assertEqual(str(cbtn['variable']), str(cbtn2['variable']))
724
725
726class MenubuttonTest(AbstractLabelTest, unittest.TestCase):
727    OPTIONS = (
728        'class', 'compound', 'cursor', 'direction',
729        'image', 'menu', 'padding', 'state', 'style',
730        'takefocus', 'text', 'textvariable',
731        'underline', 'width',
732    )
733
734    def create(self, **kwargs):
735        return ttk.Menubutton(self.root, **kwargs)
736
737    def test_direction(self):
738        widget = self.create()
739        self.checkEnumParam(widget, 'direction',
740                'above', 'below', 'left', 'right', 'flush')
741
742    def test_menu(self):
743        widget = self.create()
744        menu = tkinter.Menu(widget, name='menu')
745        self.checkParam(widget, 'menu', menu, conv=str)
746        menu.destroy()
747
748
749@add_standard_options(StandardTtkOptionsTests)
750class ScaleTest(AbstractWidgetTest, unittest.TestCase):
751    OPTIONS = (
752        'class', 'command', 'cursor', 'from', 'length',
753        'orient', 'style', 'takefocus', 'to', 'value', 'variable',
754    )
755    _conv_pixels = noconv
756    default_orient = 'horizontal'
757
758    def setUp(self):
759        super().setUp()
760        self.scale = self.create()
761        self.scale.pack()
762        self.scale.update()
763
764    def create(self, **kwargs):
765        return ttk.Scale(self.root, **kwargs)
766
767    def test_from(self):
768        widget = self.create()
769        self.checkFloatParam(widget, 'from', 100, 14.9, 15.1, conv=False)
770
771    def test_length(self):
772        widget = self.create()
773        self.checkPixelsParam(widget, 'length', 130, 131.2, 135.6, '5i')
774
775    def test_to(self):
776        widget = self.create()
777        self.checkFloatParam(widget, 'to', 300, 14.9, 15.1, -10, conv=False)
778
779    def test_value(self):
780        widget = self.create()
781        self.checkFloatParam(widget, 'value', 300, 14.9, 15.1, -10, conv=False)
782
783    def test_custom_event(self):
784        failure = [1, 1, 1] # will need to be empty
785
786        funcid = self.scale.bind('<<RangeChanged>>', lambda evt: failure.pop())
787
788        self.scale['from'] = 10
789        self.scale['from_'] = 10
790        self.scale['to'] = 3
791
792        self.assertFalse(failure)
793
794        failure = [1, 1, 1]
795        self.scale.configure(from_=2, to=5)
796        self.scale.configure(from_=0, to=-2)
797        self.scale.configure(to=10)
798
799        self.assertFalse(failure)
800
801
802    def test_get(self):
803        if self.wantobjects:
804            conv = lambda x: x
805        else:
806            conv = float
807
808        scale_width = self.scale.winfo_width()
809        self.assertEqual(self.scale.get(scale_width, 0), self.scale['to'])
810
811        self.assertEqual(conv(self.scale.get(0, 0)), conv(self.scale['from']))
812        self.assertEqual(self.scale.get(), self.scale['value'])
813        self.scale['value'] = 30
814        self.assertEqual(self.scale.get(), self.scale['value'])
815
816        self.assertRaises(tkinter.TclError, self.scale.get, '', 0)
817        self.assertRaises(tkinter.TclError, self.scale.get, 0, '')
818
819
820    def test_set(self):
821        if self.wantobjects:
822            conv = lambda x: x
823        else:
824            conv = float
825
826        # set restricts the max/min values according to the current range
827        max = conv(self.scale['to'])
828        new_max = max + 10
829        self.scale.set(new_max)
830        self.assertEqual(conv(self.scale.get()), max)
831        min = conv(self.scale['from'])
832        self.scale.set(min - 1)
833        self.assertEqual(conv(self.scale.get()), min)
834
835        # changing directly the variable doesn't impose this limitation tho
836        var = tkinter.DoubleVar(self.root)
837        self.scale['variable'] = var
838        var.set(max + 5)
839        self.assertEqual(conv(self.scale.get()), var.get())
840        self.assertEqual(conv(self.scale.get()), max + 5)
841        del var
842
843        # the same happens with the value option
844        self.scale['value'] = max + 10
845        self.assertEqual(conv(self.scale.get()), max + 10)
846        self.assertEqual(conv(self.scale.get()), conv(self.scale['value']))
847
848        # nevertheless, note that the max/min values we can get specifying
849        # x, y coords are the ones according to the current range
850        self.assertEqual(conv(self.scale.get(0, 0)), min)
851        self.assertEqual(conv(self.scale.get(self.scale.winfo_width(), 0)), max)
852
853        self.assertRaises(tkinter.TclError, self.scale.set, None)
854
855
856@add_standard_options(StandardTtkOptionsTests)
857class ProgressbarTest(AbstractWidgetTest, unittest.TestCase):
858    OPTIONS = (
859        'class', 'cursor', 'orient', 'length',
860        'mode', 'maximum', 'phase',
861        'style', 'takefocus', 'value', 'variable',
862    )
863    _conv_pixels = noconv
864    default_orient = 'horizontal'
865
866    def create(self, **kwargs):
867        return ttk.Progressbar(self.root, **kwargs)
868
869    def test_length(self):
870        widget = self.create()
871        self.checkPixelsParam(widget, 'length', 100.1, 56.7, '2i')
872
873    def test_maximum(self):
874        widget = self.create()
875        self.checkFloatParam(widget, 'maximum', 150.2, 77.7, 0, -10, conv=False)
876
877    def test_mode(self):
878        widget = self.create()
879        self.checkEnumParam(widget, 'mode', 'determinate', 'indeterminate')
880
881    def test_phase(self):
882        # XXX
883        pass
884
885    def test_value(self):
886        widget = self.create()
887        self.checkFloatParam(widget, 'value', 150.2, 77.7, 0, -10,
888                             conv=False)
889
890
891@unittest.skipIf(sys.platform == 'darwin',
892                 'ttk.Scrollbar is special on MacOSX')
893@add_standard_options(StandardTtkOptionsTests)
894class ScrollbarTest(AbstractWidgetTest, unittest.TestCase):
895    OPTIONS = (
896        'class', 'command', 'cursor', 'orient', 'style', 'takefocus',
897    )
898    default_orient = 'vertical'
899
900    def create(self, **kwargs):
901        return ttk.Scrollbar(self.root, **kwargs)
902
903
904@add_standard_options(IntegerSizeTests, StandardTtkOptionsTests)
905class NotebookTest(AbstractWidgetTest, unittest.TestCase):
906    OPTIONS = (
907        'class', 'cursor', 'height', 'padding', 'style', 'takefocus', 'width',
908    )
909
910    def setUp(self):
911        super().setUp()
912        self.nb = self.create(padding=0)
913        self.child1 = ttk.Label(self.root)
914        self.child2 = ttk.Label(self.root)
915        self.nb.add(self.child1, text='a')
916        self.nb.add(self.child2, text='b')
917
918    def create(self, **kwargs):
919        return ttk.Notebook(self.root, **kwargs)
920
921    def test_tab_identifiers(self):
922        self.nb.forget(0)
923        self.nb.hide(self.child2)
924        self.assertRaises(tkinter.TclError, self.nb.tab, self.child1)
925        self.assertEqual(self.nb.index('end'), 1)
926        self.nb.add(self.child2)
927        self.assertEqual(self.nb.index('end'), 1)
928        self.nb.select(self.child2)
929
930        self.assertTrue(self.nb.tab('current'))
931        self.nb.add(self.child1, text='a')
932
933        self.nb.pack()
934        self.nb.update()
935        if sys.platform == 'darwin':
936            tb_idx = "@20,5"
937        else:
938            tb_idx = "@5,5"
939        self.assertEqual(self.nb.tab(tb_idx), self.nb.tab('current'))
940
941        for i in range(5, 100, 5):
942            try:
943                if self.nb.tab('@%d, 5' % i, text=None) == 'a':
944                    break
945            except tkinter.TclError:
946                pass
947
948        else:
949            self.fail("Tab with text 'a' not found")
950
951
952    def test_add_and_hidden(self):
953        self.assertRaises(tkinter.TclError, self.nb.hide, -1)
954        self.assertRaises(tkinter.TclError, self.nb.hide, 'hi')
955        self.assertRaises(tkinter.TclError, self.nb.hide, None)
956        self.assertRaises(tkinter.TclError, self.nb.add, None)
957        self.assertRaises(tkinter.TclError, self.nb.add, ttk.Label(self.root),
958            unknown='option')
959
960        tabs = self.nb.tabs()
961        self.nb.hide(self.child1)
962        self.nb.add(self.child1)
963        self.assertEqual(self.nb.tabs(), tabs)
964
965        child = ttk.Label(self.root)
966        self.nb.add(child, text='c')
967        tabs = self.nb.tabs()
968
969        curr = self.nb.index('current')
970        # verify that the tab gets readded at its previous position
971        child2_index = self.nb.index(self.child2)
972        self.nb.hide(self.child2)
973        self.nb.add(self.child2)
974        self.assertEqual(self.nb.tabs(), tabs)
975        self.assertEqual(self.nb.index(self.child2), child2_index)
976        self.assertEqual(str(self.child2), self.nb.tabs()[child2_index])
977        # but the tab next to it (not hidden) is the one selected now
978        self.assertEqual(self.nb.index('current'), curr + 1)
979
980
981    def test_forget(self):
982        self.assertRaises(tkinter.TclError, self.nb.forget, -1)
983        self.assertRaises(tkinter.TclError, self.nb.forget, 'hi')
984        self.assertRaises(tkinter.TclError, self.nb.forget, None)
985
986        tabs = self.nb.tabs()
987        child1_index = self.nb.index(self.child1)
988        self.nb.forget(self.child1)
989        self.assertNotIn(str(self.child1), self.nb.tabs())
990        self.assertEqual(len(tabs) - 1, len(self.nb.tabs()))
991
992        self.nb.add(self.child1)
993        self.assertEqual(self.nb.index(self.child1), 1)
994        self.assertNotEqual(child1_index, self.nb.index(self.child1))
995
996
997    def test_index(self):
998        self.assertRaises(tkinter.TclError, self.nb.index, -1)
999        self.assertRaises(tkinter.TclError, self.nb.index, None)
1000
1001        self.assertIsInstance(self.nb.index('end'), int)
1002        self.assertEqual(self.nb.index(self.child1), 0)
1003        self.assertEqual(self.nb.index(self.child2), 1)
1004        self.assertEqual(self.nb.index('end'), 2)
1005
1006
1007    def test_insert(self):
1008        # moving tabs
1009        tabs = self.nb.tabs()
1010        self.nb.insert(1, tabs[0])
1011        self.assertEqual(self.nb.tabs(), (tabs[1], tabs[0]))
1012        self.nb.insert(self.child1, self.child2)
1013        self.assertEqual(self.nb.tabs(), tabs)
1014        self.nb.insert('end', self.child1)
1015        self.assertEqual(self.nb.tabs(), (tabs[1], tabs[0]))
1016        self.nb.insert('end', 0)
1017        self.assertEqual(self.nb.tabs(), tabs)
1018        # bad moves
1019        self.assertRaises(tkinter.TclError, self.nb.insert, 2, tabs[0])
1020        self.assertRaises(tkinter.TclError, self.nb.insert, -1, tabs[0])
1021
1022        # new tab
1023        child3 = ttk.Label(self.root)
1024        self.nb.insert(1, child3)
1025        self.assertEqual(self.nb.tabs(), (tabs[0], str(child3), tabs[1]))
1026        self.nb.forget(child3)
1027        self.assertEqual(self.nb.tabs(), tabs)
1028        self.nb.insert(self.child1, child3)
1029        self.assertEqual(self.nb.tabs(), (str(child3), ) + tabs)
1030        self.nb.forget(child3)
1031        self.assertRaises(tkinter.TclError, self.nb.insert, 2, child3)
1032        self.assertRaises(tkinter.TclError, self.nb.insert, -1, child3)
1033
1034        # bad inserts
1035        self.assertRaises(tkinter.TclError, self.nb.insert, 'end', None)
1036        self.assertRaises(tkinter.TclError, self.nb.insert, None, 0)
1037        self.assertRaises(tkinter.TclError, self.nb.insert, None, None)
1038
1039
1040    def test_select(self):
1041        self.nb.pack()
1042        self.nb.update()
1043
1044        success = []
1045        tab_changed = []
1046
1047        self.child1.bind('<Unmap>', lambda evt: success.append(True))
1048        self.nb.bind('<<NotebookTabChanged>>',
1049            lambda evt: tab_changed.append(True))
1050
1051        self.assertEqual(self.nb.select(), str(self.child1))
1052        self.nb.select(self.child2)
1053        self.assertTrue(success)
1054        self.assertEqual(self.nb.select(), str(self.child2))
1055
1056        self.nb.update()
1057        self.assertTrue(tab_changed)
1058
1059
1060    def test_tab(self):
1061        self.assertRaises(tkinter.TclError, self.nb.tab, -1)
1062        self.assertRaises(tkinter.TclError, self.nb.tab, 'notab')
1063        self.assertRaises(tkinter.TclError, self.nb.tab, None)
1064
1065        self.assertIsInstance(self.nb.tab(self.child1), dict)
1066        self.assertEqual(self.nb.tab(self.child1, text=None), 'a')
1067        # newer form for querying a single option
1068        self.assertEqual(self.nb.tab(self.child1, 'text'), 'a')
1069        self.nb.tab(self.child1, text='abc')
1070        self.assertEqual(self.nb.tab(self.child1, text=None), 'abc')
1071        self.assertEqual(self.nb.tab(self.child1, 'text'), 'abc')
1072
1073
1074    def test_tabs(self):
1075        self.assertEqual(len(self.nb.tabs()), 2)
1076
1077        self.nb.forget(self.child1)
1078        self.nb.forget(self.child2)
1079
1080        self.assertEqual(self.nb.tabs(), ())
1081
1082
1083    def test_traversal(self):
1084        self.nb.pack()
1085        self.nb.update()
1086
1087        self.nb.select(0)
1088
1089        self.assertEqual(self.nb.identify(5, 5), 'focus')
1090        simulate_mouse_click(self.nb, 5, 5)
1091        self.nb.focus_force()
1092        self.nb.event_generate('<Control-Tab>')
1093        self.assertEqual(self.nb.select(), str(self.child2))
1094        self.nb.focus_force()
1095        self.nb.event_generate('<Shift-Control-Tab>')
1096        self.assertEqual(self.nb.select(), str(self.child1))
1097        self.nb.focus_force()
1098        self.nb.event_generate('<Shift-Control-Tab>')
1099        self.assertEqual(self.nb.select(), str(self.child2))
1100
1101        self.nb.tab(self.child1, text='a', underline=0)
1102        self.nb.enable_traversal()
1103        self.nb.focus_force()
1104        self.assertEqual(self.nb.identify(5, 5), 'focus')
1105        simulate_mouse_click(self.nb, 5, 5)
1106        if sys.platform == 'darwin':
1107            self.nb.event_generate('<Option-a>')
1108        else:
1109            self.nb.event_generate('<Alt-a>')
1110        self.assertEqual(self.nb.select(), str(self.child1))
1111
1112@add_standard_options(IntegerSizeTests, StandardTtkOptionsTests)
1113class SpinboxTest(EntryTest, unittest.TestCase):
1114    OPTIONS = (
1115        'background', 'class', 'command', 'cursor', 'exportselection',
1116        'font', 'foreground', 'format', 'from',  'increment',
1117        'invalidcommand', 'justify', 'show', 'state', 'style',
1118        'takefocus', 'textvariable', 'to', 'validate', 'validatecommand',
1119        'values', 'width', 'wrap', 'xscrollcommand',
1120    )
1121
1122    def setUp(self):
1123        super().setUp()
1124        self.spin = self.create()
1125        self.spin.pack()
1126
1127    def create(self, **kwargs):
1128        return ttk.Spinbox(self.root, **kwargs)
1129
1130    def _click_increment_arrow(self):
1131        width = self.spin.winfo_width()
1132        height = self.spin.winfo_height()
1133        x = width - 5
1134        y = height//2 - 5
1135        self.assertRegex(self.spin.identify(x, y), r'.*uparrow\Z')
1136        self.spin.event_generate('<ButtonPress-1>', x=x, y=y)
1137        self.spin.event_generate('<ButtonRelease-1>', x=x, y=y)
1138        self.spin.update_idletasks()
1139
1140    def _click_decrement_arrow(self):
1141        width = self.spin.winfo_width()
1142        height = self.spin.winfo_height()
1143        x = width - 5
1144        y = height//2 + 4
1145        self.assertRegex(self.spin.identify(x, y), r'.*downarrow\Z')
1146        self.spin.event_generate('<ButtonPress-1>', x=x, y=y)
1147        self.spin.event_generate('<ButtonRelease-1>', x=x, y=y)
1148        self.spin.update_idletasks()
1149
1150    def test_command(self):
1151        success = []
1152
1153        self.spin['command'] = lambda: success.append(True)
1154        self.spin.update()
1155        self._click_increment_arrow()
1156        self.spin.update()
1157        self.assertTrue(success)
1158
1159        self._click_decrement_arrow()
1160        self.assertEqual(len(success), 2)
1161
1162        # testing postcommand removal
1163        self.spin['command'] = ''
1164        self.spin.update_idletasks()
1165        self._click_increment_arrow()
1166        self._click_decrement_arrow()
1167        self.spin.update()
1168        self.assertEqual(len(success), 2)
1169
1170    def test_to(self):
1171        self.spin['from'] = 0
1172        self.spin['to'] = 5
1173        self.spin.set(4)
1174        self.spin.update()
1175        self._click_increment_arrow()  # 5
1176
1177        self.assertEqual(self.spin.get(), '5')
1178
1179        self._click_increment_arrow()  # 5
1180        self.assertEqual(self.spin.get(), '5')
1181
1182    def test_from(self):
1183        self.spin['from'] = 1
1184        self.spin['to'] = 10
1185        self.spin.set(2)
1186        self.spin.update()
1187        self._click_decrement_arrow()  # 1
1188        self.assertEqual(self.spin.get(), '1')
1189        self._click_decrement_arrow()  # 1
1190        self.assertEqual(self.spin.get(), '1')
1191
1192    def test_increment(self):
1193        self.spin['from'] = 0
1194        self.spin['to'] = 10
1195        self.spin['increment'] = 4
1196        self.spin.set(1)
1197        self.spin.update()
1198
1199        self._click_increment_arrow()  # 5
1200        self.assertEqual(self.spin.get(), '5')
1201        self.spin['increment'] = 2
1202        self.spin.update()
1203        self._click_decrement_arrow()  # 3
1204        self.assertEqual(self.spin.get(), '3')
1205
1206    def test_format(self):
1207        self.spin.set(1)
1208        self.spin['format'] = '%10.3f'
1209        self.spin.update()
1210        self._click_increment_arrow()
1211        value = self.spin.get()
1212
1213        self.assertEqual(len(value), 10)
1214        self.assertEqual(value.index('.'), 6)
1215
1216        self.spin['format'] = ''
1217        self.spin.update()
1218        self._click_increment_arrow()
1219        value = self.spin.get()
1220        self.assertTrue('.' not in value)
1221        self.assertEqual(len(value), 1)
1222
1223    def test_wrap(self):
1224        self.spin['to'] = 10
1225        self.spin['from'] = 1
1226        self.spin.set(1)
1227        self.spin['wrap'] = True
1228        self.spin.update()
1229
1230        self._click_decrement_arrow()
1231        self.assertEqual(self.spin.get(), '10')
1232
1233        self._click_increment_arrow()
1234        self.assertEqual(self.spin.get(), '1')
1235
1236        self.spin['wrap'] = False
1237        self.spin.update()
1238
1239        self._click_decrement_arrow()
1240        self.assertEqual(self.spin.get(), '1')
1241
1242    def test_values(self):
1243        self.assertEqual(self.spin['values'],
1244                         () if tcl_version < (8, 5) else '')
1245        self.checkParam(self.spin, 'values', 'mon tue wed thur',
1246                        expected=('mon', 'tue', 'wed', 'thur'))
1247        self.checkParam(self.spin, 'values', ('mon', 'tue', 'wed', 'thur'))
1248        self.checkParam(self.spin, 'values', (42, 3.14, '', 'any string'))
1249        self.checkParam(self.spin, 'values', '')
1250
1251        self.spin['values'] = ['a', 1, 'c']
1252
1253        # test incrementing / decrementing values
1254        self.spin.set('a')
1255        self.spin.update()
1256        self._click_increment_arrow()
1257        self.assertEqual(self.spin.get(), '1')
1258
1259        self._click_decrement_arrow()
1260        self.assertEqual(self.spin.get(), 'a')
1261
1262        # testing values with empty string set through configure
1263        self.spin.configure(values=[1, '', 2])
1264        self.assertEqual(self.spin['values'],
1265                         ('1', '', '2') if self.wantobjects else
1266                         '1 {} 2')
1267
1268        # testing values with spaces
1269        self.spin['values'] = ['a b', 'a\tb', 'a\nb']
1270        self.assertEqual(self.spin['values'],
1271                         ('a b', 'a\tb', 'a\nb') if self.wantobjects else
1272                         '{a b} {a\tb} {a\nb}')
1273
1274        # testing values with special characters
1275        self.spin['values'] = [r'a\tb', '"a"', '} {']
1276        self.assertEqual(self.spin['values'],
1277                         (r'a\tb', '"a"', '} {') if self.wantobjects else
1278                         r'a\\tb {"a"} \}\ \{')
1279
1280        # testing creating spinbox with empty string in values
1281        spin2 = ttk.Spinbox(self.root, values=[1, 2, ''])
1282        self.assertEqual(spin2['values'],
1283                         ('1', '2', '') if self.wantobjects else '1 2 {}')
1284        spin2.destroy()
1285
1286
1287@add_standard_options(StandardTtkOptionsTests)
1288class TreeviewTest(AbstractWidgetTest, unittest.TestCase):
1289    OPTIONS = (
1290        'class', 'columns', 'cursor', 'displaycolumns',
1291        'height', 'padding', 'selectmode', 'show',
1292        'style', 'takefocus', 'xscrollcommand', 'yscrollcommand',
1293    )
1294
1295    def setUp(self):
1296        super().setUp()
1297        self.tv = self.create(padding=0)
1298
1299    def create(self, **kwargs):
1300        return ttk.Treeview(self.root, **kwargs)
1301
1302    def test_columns(self):
1303        widget = self.create()
1304        self.checkParam(widget, 'columns', 'a b c',
1305                        expected=('a', 'b', 'c'))
1306        self.checkParam(widget, 'columns', ('a', 'b', 'c'))
1307        self.checkParam(widget, 'columns', '')
1308
1309    def test_displaycolumns(self):
1310        widget = self.create()
1311        widget['columns'] = ('a', 'b', 'c')
1312        self.checkParam(widget, 'displaycolumns', 'b a c',
1313                        expected=('b', 'a', 'c'))
1314        self.checkParam(widget, 'displaycolumns', ('b', 'a', 'c'))
1315        self.checkParam(widget, 'displaycolumns', '#all',
1316                        expected=('#all',))
1317        self.checkParam(widget, 'displaycolumns', (2, 1, 0))
1318        self.checkInvalidParam(widget, 'displaycolumns', ('a', 'b', 'd'),
1319                               errmsg='Invalid column index d')
1320        self.checkInvalidParam(widget, 'displaycolumns', (1, 2, 3),
1321                               errmsg='Column index 3 out of bounds')
1322        self.checkInvalidParam(widget, 'displaycolumns', (1, -2),
1323                               errmsg='Column index -2 out of bounds')
1324
1325    def test_height(self):
1326        widget = self.create()
1327        self.checkPixelsParam(widget, 'height', 100, -100, 0, '3c', conv=False)
1328        self.checkPixelsParam(widget, 'height', 101.2, 102.6, conv=noconv)
1329
1330    def test_selectmode(self):
1331        widget = self.create()
1332        self.checkEnumParam(widget, 'selectmode',
1333                            'none', 'browse', 'extended')
1334
1335    def test_show(self):
1336        widget = self.create()
1337        self.checkParam(widget, 'show', 'tree headings',
1338                        expected=('tree', 'headings'))
1339        self.checkParam(widget, 'show', ('tree', 'headings'))
1340        self.checkParam(widget, 'show', ('headings', 'tree'))
1341        self.checkParam(widget, 'show', 'tree', expected=('tree',))
1342        self.checkParam(widget, 'show', 'headings', expected=('headings',))
1343
1344    def test_bbox(self):
1345        self.tv.pack()
1346        self.assertEqual(self.tv.bbox(''), '')
1347        self.tv.update()
1348
1349        item_id = self.tv.insert('', 'end')
1350        children = self.tv.get_children()
1351        self.assertTrue(children)
1352
1353        bbox = self.tv.bbox(children[0])
1354        self.assertIsBoundingBox(bbox)
1355
1356        # compare width in bboxes
1357        self.tv['columns'] = ['test']
1358        self.tv.column('test', width=50)
1359        bbox_column0 = self.tv.bbox(children[0], 0)
1360        root_width = self.tv.column('#0', width=None)
1361        if not self.wantobjects:
1362            root_width = int(root_width)
1363        self.assertEqual(bbox_column0[0], bbox[0] + root_width)
1364
1365        # verify that bbox of a closed item is the empty string
1366        child1 = self.tv.insert(item_id, 'end')
1367        self.assertEqual(self.tv.bbox(child1), '')
1368
1369
1370    def test_children(self):
1371        # no children yet, should get an empty tuple
1372        self.assertEqual(self.tv.get_children(), ())
1373
1374        item_id = self.tv.insert('', 'end')
1375        self.assertIsInstance(self.tv.get_children(), tuple)
1376        self.assertEqual(self.tv.get_children()[0], item_id)
1377
1378        # add item_id and child3 as children of child2
1379        child2 = self.tv.insert('', 'end')
1380        child3 = self.tv.insert('', 'end')
1381        self.tv.set_children(child2, item_id, child3)
1382        self.assertEqual(self.tv.get_children(child2), (item_id, child3))
1383
1384        # child3 has child2 as parent, thus trying to set child2 as a children
1385        # of child3 should result in an error
1386        self.assertRaises(tkinter.TclError,
1387            self.tv.set_children, child3, child2)
1388
1389        # remove child2 children
1390        self.tv.set_children(child2)
1391        self.assertEqual(self.tv.get_children(child2), ())
1392
1393        # remove root's children
1394        self.tv.set_children('')
1395        self.assertEqual(self.tv.get_children(), ())
1396
1397
1398    def test_column(self):
1399        # return a dict with all options/values
1400        self.assertIsInstance(self.tv.column('#0'), dict)
1401        # return a single value of the given option
1402        if self.wantobjects:
1403            self.assertIsInstance(self.tv.column('#0', width=None), int)
1404        # set a new value for an option
1405        self.tv.column('#0', width=10)
1406        # testing new way to get option value
1407        self.assertEqual(self.tv.column('#0', 'width'),
1408                         10 if self.wantobjects else '10')
1409        self.assertEqual(self.tv.column('#0', width=None),
1410                         10 if self.wantobjects else '10')
1411        # check read-only option
1412        self.assertRaises(tkinter.TclError, self.tv.column, '#0', id='X')
1413
1414        self.assertRaises(tkinter.TclError, self.tv.column, 'invalid')
1415        invalid_kws = [
1416            {'unknown_option': 'some value'},  {'stretch': 'wrong'},
1417            {'anchor': 'wrong'}, {'width': 'wrong'}, {'minwidth': 'wrong'}
1418        ]
1419        for kw in invalid_kws:
1420            self.assertRaises(tkinter.TclError, self.tv.column, '#0',
1421                **kw)
1422
1423
1424    def test_delete(self):
1425        self.assertRaises(tkinter.TclError, self.tv.delete, '#0')
1426
1427        item_id = self.tv.insert('', 'end')
1428        item2 = self.tv.insert(item_id, 'end')
1429        self.assertEqual(self.tv.get_children(), (item_id, ))
1430        self.assertEqual(self.tv.get_children(item_id), (item2, ))
1431
1432        self.tv.delete(item_id)
1433        self.assertFalse(self.tv.get_children())
1434
1435        # reattach should fail
1436        self.assertRaises(tkinter.TclError,
1437            self.tv.reattach, item_id, '', 'end')
1438
1439        # test multiple item delete
1440        item1 = self.tv.insert('', 'end')
1441        item2 = self.tv.insert('', 'end')
1442        self.assertEqual(self.tv.get_children(), (item1, item2))
1443
1444        self.tv.delete(item1, item2)
1445        self.assertFalse(self.tv.get_children())
1446
1447
1448    def test_detach_reattach(self):
1449        item_id = self.tv.insert('', 'end')
1450        item2 = self.tv.insert(item_id, 'end')
1451
1452        # calling detach without items is valid, although it does nothing
1453        prev = self.tv.get_children()
1454        self.tv.detach() # this should do nothing
1455        self.assertEqual(prev, self.tv.get_children())
1456
1457        self.assertEqual(self.tv.get_children(), (item_id, ))
1458        self.assertEqual(self.tv.get_children(item_id), (item2, ))
1459
1460        # detach item with children
1461        self.tv.detach(item_id)
1462        self.assertFalse(self.tv.get_children())
1463
1464        # reattach item with children
1465        self.tv.reattach(item_id, '', 'end')
1466        self.assertEqual(self.tv.get_children(), (item_id, ))
1467        self.assertEqual(self.tv.get_children(item_id), (item2, ))
1468
1469        # move a children to the root
1470        self.tv.move(item2, '', 'end')
1471        self.assertEqual(self.tv.get_children(), (item_id, item2))
1472        self.assertEqual(self.tv.get_children(item_id), ())
1473
1474        # bad values
1475        self.assertRaises(tkinter.TclError,
1476            self.tv.reattach, 'nonexistent', '', 'end')
1477        self.assertRaises(tkinter.TclError,
1478            self.tv.detach, 'nonexistent')
1479        self.assertRaises(tkinter.TclError,
1480            self.tv.reattach, item2, 'otherparent', 'end')
1481        self.assertRaises(tkinter.TclError,
1482            self.tv.reattach, item2, '', 'invalid')
1483
1484        # multiple detach
1485        self.tv.detach(item_id, item2)
1486        self.assertEqual(self.tv.get_children(), ())
1487        self.assertEqual(self.tv.get_children(item_id), ())
1488
1489
1490    def test_exists(self):
1491        self.assertEqual(self.tv.exists('something'), False)
1492        self.assertEqual(self.tv.exists(''), True)
1493        self.assertEqual(self.tv.exists({}), False)
1494
1495        # the following will make a tk.call equivalent to
1496        # tk.call(treeview, "exists") which should result in an error
1497        # in the tcl interpreter since tk requires an item.
1498        self.assertRaises(tkinter.TclError, self.tv.exists, None)
1499
1500
1501    def test_focus(self):
1502        # nothing is focused right now
1503        self.assertEqual(self.tv.focus(), '')
1504
1505        item1 = self.tv.insert('', 'end')
1506        self.tv.focus(item1)
1507        self.assertEqual(self.tv.focus(), item1)
1508
1509        self.tv.delete(item1)
1510        self.assertEqual(self.tv.focus(), '')
1511
1512        # try focusing inexistent item
1513        self.assertRaises(tkinter.TclError, self.tv.focus, 'hi')
1514
1515
1516    def test_heading(self):
1517        # check a dict is returned
1518        self.assertIsInstance(self.tv.heading('#0'), dict)
1519
1520        # check a value is returned
1521        self.tv.heading('#0', text='hi')
1522        self.assertEqual(self.tv.heading('#0', 'text'), 'hi')
1523        self.assertEqual(self.tv.heading('#0', text=None), 'hi')
1524
1525        # invalid option
1526        self.assertRaises(tkinter.TclError, self.tv.heading, '#0',
1527            background=None)
1528        # invalid value
1529        self.assertRaises(tkinter.TclError, self.tv.heading, '#0',
1530            anchor=1)
1531
1532    def test_heading_callback(self):
1533        def simulate_heading_click(x, y):
1534            if tcl_version >= (8, 6):
1535                self.assertEqual(self.tv.identify_column(x), '#0')
1536                self.assertEqual(self.tv.identify_region(x, y), 'heading')
1537            simulate_mouse_click(self.tv, x, y)
1538            self.tv.update()
1539
1540        success = [] # no success for now
1541
1542        self.tv.pack()
1543        self.tv.heading('#0', command=lambda: success.append(True))
1544        self.tv.column('#0', width=100)
1545        self.tv.update()
1546
1547        # assuming that the coords (5, 5) fall into heading #0
1548        simulate_heading_click(5, 5)
1549        if not success:
1550            self.fail("The command associated to the treeview heading wasn't "
1551                "invoked.")
1552
1553        success = []
1554        commands = self.tv.master._tclCommands
1555        self.tv.heading('#0', command=str(self.tv.heading('#0', command=None)))
1556        self.assertEqual(commands, self.tv.master._tclCommands)
1557        simulate_heading_click(5, 5)
1558        if not success:
1559            self.fail("The command associated to the treeview heading wasn't "
1560                "invoked.")
1561
1562        # XXX The following raises an error in a tcl interpreter, but not in
1563        # Python
1564        #self.tv.heading('#0', command='I dont exist')
1565        #simulate_heading_click(5, 5)
1566
1567
1568    def test_index(self):
1569        # item 'what' doesn't exist
1570        self.assertRaises(tkinter.TclError, self.tv.index, 'what')
1571
1572        self.assertEqual(self.tv.index(''), 0)
1573
1574        item1 = self.tv.insert('', 'end')
1575        item2 = self.tv.insert('', 'end')
1576        c1 = self.tv.insert(item1, 'end')
1577        c2 = self.tv.insert(item1, 'end')
1578        self.assertEqual(self.tv.index(item1), 0)
1579        self.assertEqual(self.tv.index(c1), 0)
1580        self.assertEqual(self.tv.index(c2), 1)
1581        self.assertEqual(self.tv.index(item2), 1)
1582
1583        self.tv.move(item2, '', 0)
1584        self.assertEqual(self.tv.index(item2), 0)
1585        self.assertEqual(self.tv.index(item1), 1)
1586
1587        # check that index still works even after its parent and siblings
1588        # have been detached
1589        self.tv.detach(item1)
1590        self.assertEqual(self.tv.index(c2), 1)
1591        self.tv.detach(c1)
1592        self.assertEqual(self.tv.index(c2), 0)
1593
1594        # but it fails after item has been deleted
1595        self.tv.delete(item1)
1596        self.assertRaises(tkinter.TclError, self.tv.index, c2)
1597
1598
1599    def test_insert_item(self):
1600        # parent 'none' doesn't exist
1601        self.assertRaises(tkinter.TclError, self.tv.insert, 'none', 'end')
1602
1603        # open values
1604        self.assertRaises(tkinter.TclError, self.tv.insert, '', 'end',
1605            open='')
1606        self.assertRaises(tkinter.TclError, self.tv.insert, '', 'end',
1607            open='please')
1608        self.assertFalse(self.tv.delete(self.tv.insert('', 'end', open=True)))
1609        self.assertFalse(self.tv.delete(self.tv.insert('', 'end', open=False)))
1610
1611        # invalid index
1612        self.assertRaises(tkinter.TclError, self.tv.insert, '', 'middle')
1613
1614        # trying to duplicate item id is invalid
1615        itemid = self.tv.insert('', 'end', 'first-item')
1616        self.assertEqual(itemid, 'first-item')
1617        self.assertRaises(tkinter.TclError, self.tv.insert, '', 'end',
1618            'first-item')
1619        self.assertRaises(tkinter.TclError, self.tv.insert, '', 'end',
1620            MockTclObj('first-item'))
1621
1622        # unicode values
1623        value = '\xe1ba'
1624        item = self.tv.insert('', 'end', values=(value, ))
1625        self.assertEqual(self.tv.item(item, 'values'),
1626                         (value,) if self.wantobjects else value)
1627        self.assertEqual(self.tv.item(item, values=None),
1628                         (value,) if self.wantobjects else value)
1629
1630        self.tv.item(item, values=self.root.splitlist(self.tv.item(item, values=None)))
1631        self.assertEqual(self.tv.item(item, values=None),
1632                         (value,) if self.wantobjects else value)
1633
1634        self.assertIsInstance(self.tv.item(item), dict)
1635
1636        # erase item values
1637        self.tv.item(item, values='')
1638        self.assertFalse(self.tv.item(item, values=None))
1639
1640        # item tags
1641        item = self.tv.insert('', 'end', tags=[1, 2, value])
1642        self.assertEqual(self.tv.item(item, tags=None),
1643                         ('1', '2', value) if self.wantobjects else
1644                         '1 2 %s' % value)
1645        self.tv.item(item, tags=[])
1646        self.assertFalse(self.tv.item(item, tags=None))
1647        self.tv.item(item, tags=(1, 2))
1648        self.assertEqual(self.tv.item(item, tags=None),
1649                         ('1', '2') if self.wantobjects else '1 2')
1650
1651        # values with spaces
1652        item = self.tv.insert('', 'end', values=('a b c',
1653            '%s %s' % (value, value)))
1654        self.assertEqual(self.tv.item(item, values=None),
1655            ('a b c', '%s %s' % (value, value)) if self.wantobjects else
1656            '{a b c} {%s %s}' % (value, value))
1657
1658        # text
1659        self.assertEqual(self.tv.item(
1660            self.tv.insert('', 'end', text="Label here"), text=None),
1661            "Label here")
1662        self.assertEqual(self.tv.item(
1663            self.tv.insert('', 'end', text=value), text=None),
1664            value)
1665
1666        # test for values which are not None
1667        itemid = self.tv.insert('', 'end', 0)
1668        self.assertEqual(itemid, '0')
1669        itemid = self.tv.insert('', 'end', 0.0)
1670        self.assertEqual(itemid, '0.0')
1671        # this is because False resolves to 0 and element with 0 iid is already present
1672        self.assertRaises(tkinter.TclError, self.tv.insert, '', 'end', False)
1673        self.assertRaises(tkinter.TclError, self.tv.insert, '', 'end', '')
1674
1675
1676    def test_selection(self):
1677        self.assertRaises(TypeError, self.tv.selection, 'spam')
1678        # item 'none' doesn't exist
1679        self.assertRaises(tkinter.TclError, self.tv.selection_set, 'none')
1680        self.assertRaises(tkinter.TclError, self.tv.selection_add, 'none')
1681        self.assertRaises(tkinter.TclError, self.tv.selection_remove, 'none')
1682        self.assertRaises(tkinter.TclError, self.tv.selection_toggle, 'none')
1683
1684        item1 = self.tv.insert('', 'end')
1685        item2 = self.tv.insert('', 'end')
1686        c1 = self.tv.insert(item1, 'end')
1687        c2 = self.tv.insert(item1, 'end')
1688        c3 = self.tv.insert(item1, 'end')
1689        self.assertEqual(self.tv.selection(), ())
1690
1691        self.tv.selection_set(c1, item2)
1692        self.assertEqual(self.tv.selection(), (c1, item2))
1693        self.tv.selection_set(c2)
1694        self.assertEqual(self.tv.selection(), (c2,))
1695
1696        self.tv.selection_add(c1, item2)
1697        self.assertEqual(self.tv.selection(), (c1, c2, item2))
1698        self.tv.selection_add(item1)
1699        self.assertEqual(self.tv.selection(), (item1, c1, c2, item2))
1700        self.tv.selection_add()
1701        self.assertEqual(self.tv.selection(), (item1, c1, c2, item2))
1702
1703        self.tv.selection_remove(item1, c3)
1704        self.assertEqual(self.tv.selection(), (c1, c2, item2))
1705        self.tv.selection_remove(c2)
1706        self.assertEqual(self.tv.selection(), (c1, item2))
1707        self.tv.selection_remove()
1708        self.assertEqual(self.tv.selection(), (c1, item2))
1709
1710        self.tv.selection_toggle(c1, c3)
1711        self.assertEqual(self.tv.selection(), (c3, item2))
1712        self.tv.selection_toggle(item2)
1713        self.assertEqual(self.tv.selection(), (c3,))
1714        self.tv.selection_toggle()
1715        self.assertEqual(self.tv.selection(), (c3,))
1716
1717        self.tv.insert('', 'end', id='with spaces')
1718        self.tv.selection_set('with spaces')
1719        self.assertEqual(self.tv.selection(), ('with spaces',))
1720
1721        self.tv.insert('', 'end', id='{brace')
1722        self.tv.selection_set('{brace')
1723        self.assertEqual(self.tv.selection(), ('{brace',))
1724
1725        self.tv.insert('', 'end', id='unicode\u20ac')
1726        self.tv.selection_set('unicode\u20ac')
1727        self.assertEqual(self.tv.selection(), ('unicode\u20ac',))
1728
1729        self.tv.insert('', 'end', id=b'bytes\xe2\x82\xac')
1730        self.tv.selection_set(b'bytes\xe2\x82\xac')
1731        self.assertEqual(self.tv.selection(), ('bytes\xe2\x82\xac',))
1732
1733        self.tv.selection_set()
1734        self.assertEqual(self.tv.selection(), ())
1735
1736        # Old interface
1737        self.tv.selection_set((c1, item2))
1738        self.assertEqual(self.tv.selection(), (c1, item2))
1739        self.tv.selection_add((c1, item1))
1740        self.assertEqual(self.tv.selection(), (item1, c1, item2))
1741        self.tv.selection_remove((item1, c3))
1742        self.assertEqual(self.tv.selection(), (c1, item2))
1743        self.tv.selection_toggle((c1, c3))
1744        self.assertEqual(self.tv.selection(), (c3, item2))
1745
1746
1747    def test_set(self):
1748        self.tv['columns'] = ['A', 'B']
1749        item = self.tv.insert('', 'end', values=['a', 'b'])
1750        self.assertEqual(self.tv.set(item), {'A': 'a', 'B': 'b'})
1751
1752        self.tv.set(item, 'B', 'a')
1753        self.assertEqual(self.tv.item(item, values=None),
1754                         ('a', 'a') if self.wantobjects else 'a a')
1755
1756        self.tv['columns'] = ['B']
1757        self.assertEqual(self.tv.set(item), {'B': 'a'})
1758
1759        self.tv.set(item, 'B', 'b')
1760        self.assertEqual(self.tv.set(item, column='B'), 'b')
1761        self.assertEqual(self.tv.item(item, values=None),
1762                         ('b', 'a') if self.wantobjects else 'b a')
1763
1764        self.tv.set(item, 'B', 123)
1765        self.assertEqual(self.tv.set(item, 'B'),
1766                         123 if self.wantobjects else '123')
1767        self.assertEqual(self.tv.item(item, values=None),
1768                         (123, 'a') if self.wantobjects else '123 a')
1769        self.assertEqual(self.tv.set(item),
1770                         {'B': 123} if self.wantobjects else {'B': '123'})
1771
1772        # inexistent column
1773        self.assertRaises(tkinter.TclError, self.tv.set, item, 'A')
1774        self.assertRaises(tkinter.TclError, self.tv.set, item, 'A', 'b')
1775
1776        # inexistent item
1777        self.assertRaises(tkinter.TclError, self.tv.set, 'notme')
1778
1779
1780    def test_tag_bind(self):
1781        events = []
1782        item1 = self.tv.insert('', 'end', tags=['call'])
1783        item2 = self.tv.insert('', 'end', tags=['call'])
1784        self.tv.tag_bind('call', '<ButtonPress-1>',
1785            lambda evt: events.append(1))
1786        self.tv.tag_bind('call', '<ButtonRelease-1>',
1787            lambda evt: events.append(2))
1788
1789        self.tv.pack()
1790        self.tv.update()
1791
1792        pos_y = set()
1793        found = set()
1794        for i in range(0, 100, 10):
1795            if len(found) == 2: # item1 and item2 already found
1796                break
1797            item_id = self.tv.identify_row(i)
1798            if item_id and item_id not in found:
1799                pos_y.add(i)
1800                found.add(item_id)
1801
1802        self.assertEqual(len(pos_y), 2) # item1 and item2 y pos
1803        for y in pos_y:
1804            simulate_mouse_click(self.tv, 0, y)
1805
1806        # by now there should be 4 things in the events list, since each
1807        # item had a bind for two events that were simulated above
1808        self.assertEqual(len(events), 4)
1809        for evt in zip(events[::2], events[1::2]):
1810            self.assertEqual(evt, (1, 2))
1811
1812
1813    def test_tag_configure(self):
1814        # Just testing parameter passing for now
1815        self.assertRaises(TypeError, self.tv.tag_configure)
1816        self.assertRaises(tkinter.TclError, self.tv.tag_configure,
1817            'test', sky='blue')
1818        self.tv.tag_configure('test', foreground='blue')
1819        self.assertEqual(str(self.tv.tag_configure('test', 'foreground')),
1820            'blue')
1821        self.assertEqual(str(self.tv.tag_configure('test', foreground=None)),
1822            'blue')
1823        self.assertIsInstance(self.tv.tag_configure('test'), dict)
1824
1825    def test_tag_has(self):
1826        item1 = self.tv.insert('', 'end', text='Item 1', tags=['tag1'])
1827        item2 = self.tv.insert('', 'end', text='Item 2', tags=['tag2'])
1828        self.assertRaises(TypeError, self.tv.tag_has)
1829        self.assertRaises(TclError, self.tv.tag_has, 'tag1', 'non-existing')
1830        self.assertTrue(self.tv.tag_has('tag1', item1))
1831        self.assertFalse(self.tv.tag_has('tag1', item2))
1832        self.assertFalse(self.tv.tag_has('tag2', item1))
1833        self.assertTrue(self.tv.tag_has('tag2', item2))
1834        self.assertFalse(self.tv.tag_has('tag3', item1))
1835        self.assertFalse(self.tv.tag_has('tag3', item2))
1836        self.assertEqual(self.tv.tag_has('tag1'), (item1,))
1837        self.assertEqual(self.tv.tag_has('tag2'), (item2,))
1838        self.assertEqual(self.tv.tag_has('tag3'), ())
1839
1840
1841@add_standard_options(StandardTtkOptionsTests)
1842class SeparatorTest(AbstractWidgetTest, unittest.TestCase):
1843    OPTIONS = (
1844        'class', 'cursor', 'orient', 'style', 'takefocus',
1845        # 'state'?
1846    )
1847    default_orient = 'horizontal'
1848
1849    def create(self, **kwargs):
1850        return ttk.Separator(self.root, **kwargs)
1851
1852
1853@add_standard_options(StandardTtkOptionsTests)
1854class SizegripTest(AbstractWidgetTest, unittest.TestCase):
1855    OPTIONS = (
1856        'class', 'cursor', 'style', 'takefocus',
1857        # 'state'?
1858    )
1859
1860    def create(self, **kwargs):
1861        return ttk.Sizegrip(self.root, **kwargs)
1862
1863tests_gui = (
1864        ButtonTest, CheckbuttonTest, ComboboxTest, EntryTest,
1865        FrameTest, LabelFrameTest, LabelTest, MenubuttonTest,
1866        NotebookTest, PanedWindowTest, ProgressbarTest,
1867        RadiobuttonTest, ScaleTest, ScrollbarTest, SeparatorTest,
1868        SizegripTest, SpinboxTest, TreeviewTest, WidgetTest,
1869        )
1870
1871if __name__ == "__main__":
1872    unittest.main()
1873