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