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