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