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