• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1"""Test configdialog, coverage 94%.
2
3Half the class creates dialog, half works with user customizations.
4"""
5from idlelib import configdialog
6from test.support import requires
7requires('gui')
8import unittest
9from unittest import mock
10from idlelib.idle_test.mock_idle import Func
11from tkinter import (Tk, StringVar, IntVar, BooleanVar, DISABLED, NORMAL)
12from idlelib import config
13from idlelib.configdialog import idleConf, changes, tracers
14
15# Tests should not depend on fortuitous user configurations.
16# They must not affect actual user .cfg files.
17# Use solution from test_config: empty parsers with no filename.
18usercfg = idleConf.userCfg
19testcfg = {
20    'main': config.IdleUserConfParser(''),
21    'highlight': config.IdleUserConfParser(''),
22    'keys': config.IdleUserConfParser(''),
23    'extensions': config.IdleUserConfParser(''),
24}
25
26root = None
27dialog = None
28mainpage = changes['main']
29highpage = changes['highlight']
30keyspage = changes['keys']
31extpage = changes['extensions']
32
33
34def setUpModule():
35    global root, dialog
36    idleConf.userCfg = testcfg
37    root = Tk()
38    # root.withdraw()    # Comment out, see issue 30870
39    dialog = configdialog.ConfigDialog(root, 'Test', _utest=True)
40
41
42def tearDownModule():
43    global root, dialog
44    idleConf.userCfg = usercfg
45    tracers.detach()
46    tracers.clear()
47    changes.clear()
48    root.update_idletasks()
49    root.destroy()
50    root = dialog = None
51
52
53class ConfigDialogTest(unittest.TestCase):
54
55    def test_deactivate_current_config(self):
56        pass
57
58    def activate_config_changes(self):
59        pass
60
61
62class ButtonTest(unittest.TestCase):
63
64    def test_click_ok(self):
65        d = dialog
66        apply = d.apply = mock.Mock()
67        destroy = d.destroy = mock.Mock()
68        d.buttons['Ok'].invoke()
69        apply.assert_called_once()
70        destroy.assert_called_once()
71        del d.destroy, d.apply
72
73    def test_click_apply(self):
74        d = dialog
75        deactivate = d.deactivate_current_config = mock.Mock()
76        save_ext = d.extpage.save_all_changed_extensions = mock.Mock()
77        activate = d.activate_config_changes = mock.Mock()
78        d.buttons['Apply'].invoke()
79        deactivate.assert_called_once()
80        save_ext.assert_called_once()
81        activate.assert_called_once()
82        del d.extpage.save_all_changed_extensions
83        del d.activate_config_changes, d.deactivate_current_config
84
85    def test_click_cancel(self):
86        d = dialog
87        d.destroy = Func()
88        changes['main']['something'] = 1
89        d.buttons['Cancel'].invoke()
90        self.assertEqual(changes['main'], {})
91        self.assertEqual(d.destroy.called, 1)
92        del d.destroy
93
94    def test_click_help(self):
95        dialog.note.select(dialog.keyspage)
96        with mock.patch.object(configdialog, 'view_text',
97                               new_callable=Func) as view:
98            dialog.buttons['Help'].invoke()
99            title, contents = view.kwds['title'], view.kwds['contents']
100        self.assertEqual(title, 'Help for IDLE preferences')
101        self.assertTrue(contents.startswith('When you click') and
102                        contents.endswith('a different name.\n'))
103
104
105class FontPageTest(unittest.TestCase):
106    """Test that font widgets enable users to make font changes.
107
108    Test that widget actions set vars, that var changes add three
109    options to changes and call set_samples, and that set_samples
110    changes the font of both sample boxes.
111    """
112    @classmethod
113    def setUpClass(cls):
114        page = cls.page = dialog.fontpage
115        dialog.note.select(page)
116        page.set_samples = Func()  # Mask instance method.
117        page.update()
118
119    @classmethod
120    def tearDownClass(cls):
121        del cls.page.set_samples  # Unmask instance method.
122
123    def setUp(self):
124        changes.clear()
125
126    def test_load_font_cfg(self):
127        # Leave widget load test to human visual check.
128        # TODO Improve checks when add IdleConf.get_font_values.
129        tracers.detach()
130        d = self.page
131        d.font_name.set('Fake')
132        d.font_size.set('1')
133        d.font_bold.set(True)
134        d.set_samples.called = 0
135        d.load_font_cfg()
136        self.assertNotEqual(d.font_name.get(), 'Fake')
137        self.assertNotEqual(d.font_size.get(), '1')
138        self.assertFalse(d.font_bold.get())
139        self.assertEqual(d.set_samples.called, 1)
140        tracers.attach()
141
142    def test_fontlist_key(self):
143        # Up and Down keys should select a new font.
144        d = self.page
145        if d.fontlist.size() < 2:
146            self.skipTest('need at least 2 fonts')
147        fontlist = d.fontlist
148        fontlist.activate(0)
149        font = d.fontlist.get('active')
150
151        # Test Down key.
152        fontlist.focus_force()
153        fontlist.update()
154        fontlist.event_generate('<Key-Down>')
155        fontlist.event_generate('<KeyRelease-Down>')
156
157        down_font = fontlist.get('active')
158        self.assertNotEqual(down_font, font)
159        self.assertIn(d.font_name.get(), down_font.lower())
160
161        # Test Up key.
162        fontlist.focus_force()
163        fontlist.update()
164        fontlist.event_generate('<Key-Up>')
165        fontlist.event_generate('<KeyRelease-Up>')
166
167        up_font = fontlist.get('active')
168        self.assertEqual(up_font, font)
169        self.assertIn(d.font_name.get(), up_font.lower())
170
171    def test_fontlist_mouse(self):
172        # Click on item should select that item.
173        d = self.page
174        if d.fontlist.size() < 2:
175            self.skipTest('need at least 2 fonts')
176        fontlist = d.fontlist
177        fontlist.activate(0)
178
179        # Select next item in listbox
180        fontlist.focus_force()
181        fontlist.see(1)
182        fontlist.update()
183        x, y, dx, dy = fontlist.bbox(1)
184        x += dx // 2
185        y += dy // 2
186        fontlist.event_generate('<Button-1>', x=x, y=y)
187        fontlist.event_generate('<ButtonRelease-1>', x=x, y=y)
188
189        font1 = fontlist.get(1)
190        select_font = fontlist.get('anchor')
191        self.assertEqual(select_font, font1)
192        self.assertIn(d.font_name.get(), font1.lower())
193
194    def test_sizelist(self):
195        # Click on number should select that number
196        d = self.page
197        d.sizelist.variable.set(40)
198        self.assertEqual(d.font_size.get(), '40')
199
200    def test_bold_toggle(self):
201        # Click on checkbutton should invert it.
202        d = self.page
203        d.font_bold.set(False)
204        d.bold_toggle.invoke()
205        self.assertTrue(d.font_bold.get())
206        d.bold_toggle.invoke()
207        self.assertFalse(d.font_bold.get())
208
209    def test_font_set(self):
210        # Test that setting a font Variable results in 3 provisional
211        # change entries and a call to set_samples. Use values sure to
212        # not be defaults.
213
214        default_font = idleConf.GetFont(root, 'main', 'EditorWindow')
215        default_size = str(default_font[1])
216        default_bold = default_font[2] == 'bold'
217        d = self.page
218        d.font_size.set(default_size)
219        d.font_bold.set(default_bold)
220        d.set_samples.called = 0
221
222        d.font_name.set('Test Font')
223        expected = {'EditorWindow': {'font': 'Test Font',
224                                     'font-size': default_size,
225                                     'font-bold': str(default_bold)}}
226        self.assertEqual(mainpage, expected)
227        self.assertEqual(d.set_samples.called, 1)
228        changes.clear()
229
230        d.font_size.set('20')
231        expected = {'EditorWindow': {'font': 'Test Font',
232                                     'font-size': '20',
233                                     'font-bold': str(default_bold)}}
234        self.assertEqual(mainpage, expected)
235        self.assertEqual(d.set_samples.called, 2)
236        changes.clear()
237
238        d.font_bold.set(not default_bold)
239        expected = {'EditorWindow': {'font': 'Test Font',
240                                     'font-size': '20',
241                                     'font-bold': str(not default_bold)}}
242        self.assertEqual(mainpage, expected)
243        self.assertEqual(d.set_samples.called, 3)
244
245    def test_set_samples(self):
246        d = self.page
247        del d.set_samples  # Unmask method for test
248        orig_samples = d.font_sample, d.highlight_sample
249        d.font_sample, d.highlight_sample = {}, {}
250        d.font_name.set('test')
251        d.font_size.set('5')
252        d.font_bold.set(1)
253        expected = {'font': ('test', '5', 'bold')}
254
255        # Test set_samples.
256        d.set_samples()
257        self.assertTrue(d.font_sample == d.highlight_sample == expected)
258
259        d.font_sample, d.highlight_sample = orig_samples
260        d.set_samples = Func()  # Re-mask for other tests.
261
262
263class HighPageTest(unittest.TestCase):
264    """Test that highlight tab widgets enable users to make changes.
265
266    Test that widget actions set vars, that var changes add
267    options to changes and that themes work correctly.
268    """
269
270    @classmethod
271    def setUpClass(cls):
272        page = cls.page = dialog.highpage
273        dialog.note.select(page)
274        page.set_theme_type = Func()
275        page.paint_theme_sample = Func()
276        page.set_highlight_target = Func()
277        page.set_color_sample = Func()
278        page.update()
279
280    @classmethod
281    def tearDownClass(cls):
282        d = cls.page
283        del d.set_theme_type, d.paint_theme_sample
284        del d.set_highlight_target, d.set_color_sample
285
286    def setUp(self):
287        d = self.page
288        # The following is needed for test_load_key_cfg, _delete_custom_keys.
289        # This may indicate a defect in some test or function.
290        for section in idleConf.GetSectionList('user', 'highlight'):
291            idleConf.userCfg['highlight'].remove_section(section)
292        changes.clear()
293        d.set_theme_type.called = 0
294        d.paint_theme_sample.called = 0
295        d.set_highlight_target.called = 0
296        d.set_color_sample.called = 0
297
298    def test_load_theme_cfg(self):
299        tracers.detach()
300        d = self.page
301        eq = self.assertEqual
302
303        # Use builtin theme with no user themes created.
304        idleConf.CurrentTheme = mock.Mock(return_value='IDLE Classic')
305        d.load_theme_cfg()
306        self.assertTrue(d.theme_source.get())
307        # builtinlist sets variable builtin_name to the CurrentTheme default.
308        eq(d.builtin_name.get(), 'IDLE Classic')
309        eq(d.custom_name.get(), '- no custom themes -')
310        eq(d.custom_theme_on.state(), ('disabled',))
311        eq(d.set_theme_type.called, 1)
312        eq(d.paint_theme_sample.called, 1)
313        eq(d.set_highlight_target.called, 1)
314
315        # Builtin theme with non-empty user theme list.
316        idleConf.SetOption('highlight', 'test1', 'option', 'value')
317        idleConf.SetOption('highlight', 'test2', 'option2', 'value2')
318        d.load_theme_cfg()
319        eq(d.builtin_name.get(), 'IDLE Classic')
320        eq(d.custom_name.get(), 'test1')
321        eq(d.set_theme_type.called, 2)
322        eq(d.paint_theme_sample.called, 2)
323        eq(d.set_highlight_target.called, 2)
324
325        # Use custom theme.
326        idleConf.CurrentTheme = mock.Mock(return_value='test2')
327        idleConf.SetOption('main', 'Theme', 'default', '0')
328        d.load_theme_cfg()
329        self.assertFalse(d.theme_source.get())
330        eq(d.builtin_name.get(), 'IDLE Classic')
331        eq(d.custom_name.get(), 'test2')
332        eq(d.set_theme_type.called, 3)
333        eq(d.paint_theme_sample.called, 3)
334        eq(d.set_highlight_target.called, 3)
335
336        del idleConf.CurrentTheme
337        tracers.attach()
338
339    def test_theme_source(self):
340        eq = self.assertEqual
341        d = self.page
342        # Test these separately.
343        d.var_changed_builtin_name = Func()
344        d.var_changed_custom_name = Func()
345        # Builtin selected.
346        d.builtin_theme_on.invoke()
347        eq(mainpage, {'Theme': {'default': 'True'}})
348        eq(d.var_changed_builtin_name.called, 1)
349        eq(d.var_changed_custom_name.called, 0)
350        changes.clear()
351
352        # Custom selected.
353        d.custom_theme_on.state(('!disabled',))
354        d.custom_theme_on.invoke()
355        self.assertEqual(mainpage, {'Theme': {'default': 'False'}})
356        eq(d.var_changed_builtin_name.called, 1)
357        eq(d.var_changed_custom_name.called, 1)
358        del d.var_changed_builtin_name, d.var_changed_custom_name
359
360    def test_builtin_name(self):
361        eq = self.assertEqual
362        d = self.page
363        item_list = ['IDLE Classic', 'IDLE Dark', 'IDLE New']
364
365        # Not in old_themes, defaults name to first item.
366        idleConf.SetOption('main', 'Theme', 'name', 'spam')
367        d.builtinlist.SetMenu(item_list, 'IDLE Dark')
368        eq(mainpage, {'Theme': {'name': 'IDLE Classic',
369                                'name2': 'IDLE Dark'}})
370        eq(d.theme_message['text'], 'New theme, see Help')
371        eq(d.paint_theme_sample.called, 1)
372
373        # Not in old themes - uses name2.
374        changes.clear()
375        idleConf.SetOption('main', 'Theme', 'name', 'IDLE New')
376        d.builtinlist.SetMenu(item_list, 'IDLE Dark')
377        eq(mainpage, {'Theme': {'name2': 'IDLE Dark'}})
378        eq(d.theme_message['text'], 'New theme, see Help')
379        eq(d.paint_theme_sample.called, 2)
380
381        # Builtin name in old_themes.
382        changes.clear()
383        d.builtinlist.SetMenu(item_list, 'IDLE Classic')
384        eq(mainpage, {'Theme': {'name': 'IDLE Classic', 'name2': ''}})
385        eq(d.theme_message['text'], '')
386        eq(d.paint_theme_sample.called, 3)
387
388    def test_custom_name(self):
389        d = self.page
390
391        # If no selections, doesn't get added.
392        d.customlist.SetMenu([], '- no custom themes -')
393        self.assertNotIn('Theme', mainpage)
394        self.assertEqual(d.paint_theme_sample.called, 0)
395
396        # Custom name selected.
397        changes.clear()
398        d.customlist.SetMenu(['a', 'b', 'c'], 'c')
399        self.assertEqual(mainpage, {'Theme': {'name': 'c'}})
400        self.assertEqual(d.paint_theme_sample.called, 1)
401
402    def test_color(self):
403        d = self.page
404        d.on_new_color_set = Func()
405        # self.color is only set in get_color through colorchooser.
406        d.color.set('green')
407        self.assertEqual(d.on_new_color_set.called, 1)
408        del d.on_new_color_set
409
410    def test_highlight_target_list_mouse(self):
411        # Set highlight_target through targetlist.
412        eq = self.assertEqual
413        d = self.page
414
415        d.targetlist.SetMenu(['a', 'b', 'c'], 'c')
416        eq(d.highlight_target.get(), 'c')
417        eq(d.set_highlight_target.called, 1)
418
419    def test_highlight_target_text_mouse(self):
420        # Set highlight_target through clicking highlight_sample.
421        eq = self.assertEqual
422        d = self.page
423
424        elem = {}
425        count = 0
426        hs = d.highlight_sample
427        hs.focus_force()
428        hs.see(1.0)
429        hs.update_idletasks()
430
431        def tag_to_element(elem):
432            for element, tag in d.theme_elements.items():
433                elem[tag[0]] = element
434
435        def click_it(start):
436            x, y, dx, dy = hs.bbox(start)
437            x += dx // 2
438            y += dy // 2
439            hs.event_generate('<Enter>', x=0, y=0)
440            hs.event_generate('<Motion>', x=x, y=y)
441            hs.event_generate('<ButtonPress-1>', x=x, y=y)
442            hs.event_generate('<ButtonRelease-1>', x=x, y=y)
443
444        # Flip theme_elements to make the tag the key.
445        tag_to_element(elem)
446
447        # If highlight_sample has a tag that isn't in theme_elements, there
448        # will be a KeyError in the test run.
449        for tag in hs.tag_names():
450            for start_index in hs.tag_ranges(tag)[0::2]:
451                count += 1
452                click_it(start_index)
453                eq(d.highlight_target.get(), elem[tag])
454                eq(d.set_highlight_target.called, count)
455
456    def test_highlight_sample_double_click(self):
457        # Test double click on highlight_sample.
458        eq = self.assertEqual
459        d = self.page
460
461        hs = d.highlight_sample
462        hs.focus_force()
463        hs.see(1.0)
464        hs.update_idletasks()
465
466        # Test binding from configdialog.
467        hs.event_generate('<Enter>', x=0, y=0)
468        hs.event_generate('<Motion>', x=0, y=0)
469        # Double click is a sequence of two clicks in a row.
470        for _ in range(2):
471            hs.event_generate('<ButtonPress-1>', x=0, y=0)
472            hs.event_generate('<ButtonRelease-1>', x=0, y=0)
473
474        eq(hs.tag_ranges('sel'), ())
475
476    def test_highlight_sample_b1_motion(self):
477        # Test button motion on highlight_sample.
478        eq = self.assertEqual
479        d = self.page
480
481        hs = d.highlight_sample
482        hs.focus_force()
483        hs.see(1.0)
484        hs.update_idletasks()
485
486        x, y, dx, dy, offset = hs.dlineinfo('1.0')
487
488        # Test binding from configdialog.
489        hs.event_generate('<Leave>')
490        hs.event_generate('<Enter>')
491        hs.event_generate('<Motion>', x=x, y=y)
492        hs.event_generate('<ButtonPress-1>', x=x, y=y)
493        hs.event_generate('<B1-Motion>', x=dx, y=dy)
494        hs.event_generate('<ButtonRelease-1>', x=dx, y=dy)
495
496        eq(hs.tag_ranges('sel'), ())
497
498    def test_set_theme_type(self):
499        eq = self.assertEqual
500        d = self.page
501        del d.set_theme_type
502
503        # Builtin theme selected.
504        d.theme_source.set(True)
505        d.set_theme_type()
506        eq(d.builtinlist['state'], NORMAL)
507        eq(d.customlist['state'], DISABLED)
508        eq(d.button_delete_custom.state(), ('disabled',))
509
510        # Custom theme selected.
511        d.theme_source.set(False)
512        d.set_theme_type()
513        eq(d.builtinlist['state'], DISABLED)
514        eq(d.custom_theme_on.state(), ('selected',))
515        eq(d.customlist['state'], NORMAL)
516        eq(d.button_delete_custom.state(), ())
517        d.set_theme_type = Func()
518
519    def test_get_color(self):
520        eq = self.assertEqual
521        d = self.page
522        orig_chooser = configdialog.colorchooser.askcolor
523        chooser = configdialog.colorchooser.askcolor = Func()
524        gntn = d.get_new_theme_name = Func()
525
526        d.highlight_target.set('Editor Breakpoint')
527        d.color.set('#ffffff')
528
529        # Nothing selected.
530        chooser.result = (None, None)
531        d.button_set_color.invoke()
532        eq(d.color.get(), '#ffffff')
533
534        # Selection same as previous color.
535        chooser.result = ('', d.style.lookup(d.frame_color_set['style'], 'background'))
536        d.button_set_color.invoke()
537        eq(d.color.get(), '#ffffff')
538
539        # Select different color.
540        chooser.result = ((222.8671875, 0.0, 0.0), '#de0000')
541
542        # Default theme.
543        d.color.set('#ffffff')
544        d.theme_source.set(True)
545
546        # No theme name selected therefore color not saved.
547        gntn.result = ''
548        d.button_set_color.invoke()
549        eq(gntn.called, 1)
550        eq(d.color.get(), '#ffffff')
551        # Theme name selected.
552        gntn.result = 'My New Theme'
553        d.button_set_color.invoke()
554        eq(d.custom_name.get(), gntn.result)
555        eq(d.color.get(), '#de0000')
556
557        # Custom theme.
558        d.color.set('#ffffff')
559        d.theme_source.set(False)
560        d.button_set_color.invoke()
561        eq(d.color.get(), '#de0000')
562
563        del d.get_new_theme_name
564        configdialog.colorchooser.askcolor = orig_chooser
565
566    def test_on_new_color_set(self):
567        d = self.page
568        color = '#3f7cae'
569        d.custom_name.set('Python')
570        d.highlight_target.set('Selected Text')
571        d.fg_bg_toggle.set(True)
572
573        d.color.set(color)
574        self.assertEqual(d.style.lookup(d.frame_color_set['style'], 'background'), color)
575        self.assertEqual(d.highlight_sample.tag_cget('hilite', 'foreground'), color)
576        self.assertEqual(highpage,
577                         {'Python': {'hilite-foreground': color}})
578
579    def test_get_new_theme_name(self):
580        orig_sectionname = configdialog.SectionName
581        sn = configdialog.SectionName = Func(return_self=True)
582        d = self.page
583
584        sn.result = 'New Theme'
585        self.assertEqual(d.get_new_theme_name(''), 'New Theme')
586
587        configdialog.SectionName = orig_sectionname
588
589    def test_save_as_new_theme(self):
590        d = self.page
591        gntn = d.get_new_theme_name = Func()
592        d.theme_source.set(True)
593
594        # No name entered.
595        gntn.result = ''
596        d.button_save_custom.invoke()
597        self.assertNotIn(gntn.result, idleConf.userCfg['highlight'])
598
599        # Name entered.
600        gntn.result = 'my new theme'
601        gntn.called = 0
602        self.assertNotIn(gntn.result, idleConf.userCfg['highlight'])
603        d.button_save_custom.invoke()
604        self.assertIn(gntn.result, idleConf.userCfg['highlight'])
605
606        del d.get_new_theme_name
607
608    def test_create_new_and_save_new(self):
609        eq = self.assertEqual
610        d = self.page
611
612        # Use default as previously active theme.
613        d.theme_source.set(True)
614        d.builtin_name.set('IDLE Classic')
615        first_new = 'my new custom theme'
616        second_new = 'my second custom theme'
617
618        # No changes, so themes are an exact copy.
619        self.assertNotIn(first_new, idleConf.userCfg)
620        d.create_new(first_new)
621        eq(idleConf.GetSectionList('user', 'highlight'), [first_new])
622        eq(idleConf.GetThemeDict('default', 'IDLE Classic'),
623           idleConf.GetThemeDict('user', first_new))
624        eq(d.custom_name.get(), first_new)
625        self.assertFalse(d.theme_source.get())  # Use custom set.
626        eq(d.set_theme_type.called, 1)
627
628        # Test that changed targets are in new theme.
629        changes.add_option('highlight', first_new, 'hit-background', 'yellow')
630        self.assertNotIn(second_new, idleConf.userCfg)
631        d.create_new(second_new)
632        eq(idleConf.GetSectionList('user', 'highlight'), [first_new, second_new])
633        self.assertNotEqual(idleConf.GetThemeDict('user', first_new),
634                            idleConf.GetThemeDict('user', second_new))
635        # Check that difference in themes was in `hit-background` from `changes`.
636        idleConf.SetOption('highlight', first_new, 'hit-background', 'yellow')
637        eq(idleConf.GetThemeDict('user', first_new),
638           idleConf.GetThemeDict('user', second_new))
639
640    def test_set_highlight_target(self):
641        eq = self.assertEqual
642        d = self.page
643        del d.set_highlight_target
644
645        # Target is cursor.
646        d.highlight_target.set('Cursor')
647        eq(d.fg_on.state(), ('disabled', 'selected'))
648        eq(d.bg_on.state(), ('disabled',))
649        self.assertTrue(d.fg_bg_toggle)
650        eq(d.set_color_sample.called, 1)
651
652        # Target is not cursor.
653        d.highlight_target.set('Comment')
654        eq(d.fg_on.state(), ('selected',))
655        eq(d.bg_on.state(), ())
656        self.assertTrue(d.fg_bg_toggle)
657        eq(d.set_color_sample.called, 2)
658
659        d.set_highlight_target = Func()
660
661    def test_set_color_sample_binding(self):
662        d = self.page
663        scs = d.set_color_sample
664
665        d.fg_on.invoke()
666        self.assertEqual(scs.called, 1)
667
668        d.bg_on.invoke()
669        self.assertEqual(scs.called, 2)
670
671    def test_set_color_sample(self):
672        d = self.page
673        del d.set_color_sample
674        d.highlight_target.set('Selected Text')
675        d.fg_bg_toggle.set(True)
676        d.set_color_sample()
677        self.assertEqual(
678                d.style.lookup(d.frame_color_set['style'], 'background'),
679                d.highlight_sample.tag_cget('hilite', 'foreground'))
680        d.set_color_sample = Func()
681
682    def test_paint_theme_sample(self):
683        eq = self.assertEqual
684        page = self.page
685        del page.paint_theme_sample  # Delete masking mock.
686        hs_tag = page.highlight_sample.tag_cget
687        gh = idleConf.GetHighlight
688
689        # Create custom theme based on IDLE Dark.
690        page.theme_source.set(True)
691        page.builtin_name.set('IDLE Dark')
692        theme = 'IDLE Test'
693        page.create_new(theme)
694        page.set_color_sample.called = 0
695
696        # Base theme with nothing in `changes`.
697        page.paint_theme_sample()
698        new_console = {'foreground': 'blue',
699                       'background': 'yellow',}
700        for key, value in new_console.items():
701            self.assertNotEqual(hs_tag('console', key), value)
702        eq(page.set_color_sample.called, 1)
703
704        # Apply changes.
705        for key, value in new_console.items():
706            changes.add_option('highlight', theme, 'console-'+key, value)
707        page.paint_theme_sample()
708        for key, value in new_console.items():
709            eq(hs_tag('console', key), value)
710        eq(page.set_color_sample.called, 2)
711
712        page.paint_theme_sample = Func()
713
714    def test_delete_custom(self):
715        eq = self.assertEqual
716        d = self.page
717        d.button_delete_custom.state(('!disabled',))
718        yesno = d.askyesno = Func()
719        dialog.deactivate_current_config = Func()
720        dialog.activate_config_changes = Func()
721
722        theme_name = 'spam theme'
723        idleConf.userCfg['highlight'].SetOption(theme_name, 'name', 'value')
724        highpage[theme_name] = {'option': 'True'}
725
726        theme_name2 = 'other theme'
727        idleConf.userCfg['highlight'].SetOption(theme_name2, 'name', 'value')
728        highpage[theme_name2] = {'option': 'False'}
729
730        # Force custom theme.
731        d.custom_theme_on.state(('!disabled',))
732        d.custom_theme_on.invoke()
733        d.custom_name.set(theme_name)
734
735        # Cancel deletion.
736        yesno.result = False
737        d.button_delete_custom.invoke()
738        eq(yesno.called, 1)
739        eq(highpage[theme_name], {'option': 'True'})
740        eq(idleConf.GetSectionList('user', 'highlight'), [theme_name, theme_name2])
741        eq(dialog.deactivate_current_config.called, 0)
742        eq(dialog.activate_config_changes.called, 0)
743        eq(d.set_theme_type.called, 0)
744
745        # Confirm deletion.
746        yesno.result = True
747        d.button_delete_custom.invoke()
748        eq(yesno.called, 2)
749        self.assertNotIn(theme_name, highpage)
750        eq(idleConf.GetSectionList('user', 'highlight'), [theme_name2])
751        eq(d.custom_theme_on.state(), ())
752        eq(d.custom_name.get(), theme_name2)
753        eq(dialog.deactivate_current_config.called, 1)
754        eq(dialog.activate_config_changes.called, 1)
755        eq(d.set_theme_type.called, 1)
756
757        # Confirm deletion of second theme - empties list.
758        d.custom_name.set(theme_name2)
759        yesno.result = True
760        d.button_delete_custom.invoke()
761        eq(yesno.called, 3)
762        self.assertNotIn(theme_name, highpage)
763        eq(idleConf.GetSectionList('user', 'highlight'), [])
764        eq(d.custom_theme_on.state(), ('disabled',))
765        eq(d.custom_name.get(), '- no custom themes -')
766        eq(dialog.deactivate_current_config.called, 2)
767        eq(dialog.activate_config_changes.called, 2)
768        eq(d.set_theme_type.called, 2)
769
770        del dialog.activate_config_changes, dialog.deactivate_current_config
771        del d.askyesno
772
773
774class KeysPageTest(unittest.TestCase):
775    """Test that keys tab widgets enable users to make changes.
776
777    Test that widget actions set vars, that var changes add
778    options to changes and that key sets works correctly.
779    """
780
781    @classmethod
782    def setUpClass(cls):
783        page = cls.page = dialog.keyspage
784        dialog.note.select(page)
785        page.set_keys_type = Func()
786        page.load_keys_list = Func()
787
788    @classmethod
789    def tearDownClass(cls):
790        page = cls.page
791        del page.set_keys_type, page.load_keys_list
792
793    def setUp(self):
794        d = self.page
795        # The following is needed for test_load_key_cfg, _delete_custom_keys.
796        # This may indicate a defect in some test or function.
797        for section in idleConf.GetSectionList('user', 'keys'):
798            idleConf.userCfg['keys'].remove_section(section)
799        changes.clear()
800        d.set_keys_type.called = 0
801        d.load_keys_list.called = 0
802
803    def test_load_key_cfg(self):
804        tracers.detach()
805        d = self.page
806        eq = self.assertEqual
807
808        # Use builtin keyset with no user keysets created.
809        idleConf.CurrentKeys = mock.Mock(return_value='IDLE Classic OSX')
810        d.load_key_cfg()
811        self.assertTrue(d.keyset_source.get())
812        # builtinlist sets variable builtin_name to the CurrentKeys default.
813        eq(d.builtin_name.get(), 'IDLE Classic OSX')
814        eq(d.custom_name.get(), '- no custom keys -')
815        eq(d.custom_keyset_on.state(), ('disabled',))
816        eq(d.set_keys_type.called, 1)
817        eq(d.load_keys_list.called, 1)
818        eq(d.load_keys_list.args, ('IDLE Classic OSX', ))
819
820        # Builtin keyset with non-empty user keyset list.
821        idleConf.SetOption('keys', 'test1', 'option', 'value')
822        idleConf.SetOption('keys', 'test2', 'option2', 'value2')
823        d.load_key_cfg()
824        eq(d.builtin_name.get(), 'IDLE Classic OSX')
825        eq(d.custom_name.get(), 'test1')
826        eq(d.set_keys_type.called, 2)
827        eq(d.load_keys_list.called, 2)
828        eq(d.load_keys_list.args, ('IDLE Classic OSX', ))
829
830        # Use custom keyset.
831        idleConf.CurrentKeys = mock.Mock(return_value='test2')
832        idleConf.default_keys = mock.Mock(return_value='IDLE Modern Unix')
833        idleConf.SetOption('main', 'Keys', 'default', '0')
834        d.load_key_cfg()
835        self.assertFalse(d.keyset_source.get())
836        eq(d.builtin_name.get(), 'IDLE Modern Unix')
837        eq(d.custom_name.get(), 'test2')
838        eq(d.set_keys_type.called, 3)
839        eq(d.load_keys_list.called, 3)
840        eq(d.load_keys_list.args, ('test2', ))
841
842        del idleConf.CurrentKeys, idleConf.default_keys
843        tracers.attach()
844
845    def test_keyset_source(self):
846        eq = self.assertEqual
847        d = self.page
848        # Test these separately.
849        d.var_changed_builtin_name = Func()
850        d.var_changed_custom_name = Func()
851        # Builtin selected.
852        d.builtin_keyset_on.invoke()
853        eq(mainpage, {'Keys': {'default': 'True'}})
854        eq(d.var_changed_builtin_name.called, 1)
855        eq(d.var_changed_custom_name.called, 0)
856        changes.clear()
857
858        # Custom selected.
859        d.custom_keyset_on.state(('!disabled',))
860        d.custom_keyset_on.invoke()
861        self.assertEqual(mainpage, {'Keys': {'default': 'False'}})
862        eq(d.var_changed_builtin_name.called, 1)
863        eq(d.var_changed_custom_name.called, 1)
864        del d.var_changed_builtin_name, d.var_changed_custom_name
865
866    def test_builtin_name(self):
867        eq = self.assertEqual
868        d = self.page
869        idleConf.userCfg['main'].remove_section('Keys')
870        item_list = ['IDLE Classic Windows', 'IDLE Classic OSX',
871                     'IDLE Modern UNIX']
872
873        # Not in old_keys, defaults name to first item.
874        d.builtinlist.SetMenu(item_list, 'IDLE Modern UNIX')
875        eq(mainpage, {'Keys': {'name': 'IDLE Classic Windows',
876                               'name2': 'IDLE Modern UNIX'}})
877        eq(d.keys_message['text'], 'New key set, see Help')
878        eq(d.load_keys_list.called, 1)
879        eq(d.load_keys_list.args, ('IDLE Modern UNIX', ))
880
881        # Not in old keys - uses name2.
882        changes.clear()
883        idleConf.SetOption('main', 'Keys', 'name', 'IDLE Classic Unix')
884        d.builtinlist.SetMenu(item_list, 'IDLE Modern UNIX')
885        eq(mainpage, {'Keys': {'name2': 'IDLE Modern UNIX'}})
886        eq(d.keys_message['text'], 'New key set, see Help')
887        eq(d.load_keys_list.called, 2)
888        eq(d.load_keys_list.args, ('IDLE Modern UNIX', ))
889
890        # Builtin name in old_keys.
891        changes.clear()
892        d.builtinlist.SetMenu(item_list, 'IDLE Classic OSX')
893        eq(mainpage, {'Keys': {'name': 'IDLE Classic OSX', 'name2': ''}})
894        eq(d.keys_message['text'], '')
895        eq(d.load_keys_list.called, 3)
896        eq(d.load_keys_list.args, ('IDLE Classic OSX', ))
897
898    def test_custom_name(self):
899        d = self.page
900
901        # If no selections, doesn't get added.
902        d.customlist.SetMenu([], '- no custom keys -')
903        self.assertNotIn('Keys', mainpage)
904        self.assertEqual(d.load_keys_list.called, 0)
905
906        # Custom name selected.
907        changes.clear()
908        d.customlist.SetMenu(['a', 'b', 'c'], 'c')
909        self.assertEqual(mainpage, {'Keys': {'name': 'c'}})
910        self.assertEqual(d.load_keys_list.called, 1)
911
912    def test_keybinding(self):
913        idleConf.SetOption('extensions', 'ZzDummy', 'enable', 'True')
914        d = self.page
915        d.custom_name.set('my custom keys')
916        d.bindingslist.delete(0, 'end')
917        d.bindingslist.insert(0, 'copy')
918        d.bindingslist.insert(1, 'z-in')
919        d.bindingslist.selection_set(0)
920        d.bindingslist.selection_anchor(0)
921        # Core binding - adds to keys.
922        d.keybinding.set('<Key-F11>')
923        self.assertEqual(keyspage,
924                         {'my custom keys': {'copy': '<Key-F11>'}})
925
926        # Not a core binding - adds to extensions.
927        d.bindingslist.selection_set(1)
928        d.bindingslist.selection_anchor(1)
929        d.keybinding.set('<Key-F11>')
930        self.assertEqual(extpage,
931                         {'ZzDummy_cfgBindings': {'z-in': '<Key-F11>'}})
932
933    def test_set_keys_type(self):
934        eq = self.assertEqual
935        d = self.page
936        del d.set_keys_type
937
938        # Builtin keyset selected.
939        d.keyset_source.set(True)
940        d.set_keys_type()
941        eq(d.builtinlist['state'], NORMAL)
942        eq(d.customlist['state'], DISABLED)
943        eq(d.button_delete_custom_keys.state(), ('disabled',))
944
945        # Custom keyset selected.
946        d.keyset_source.set(False)
947        d.set_keys_type()
948        eq(d.builtinlist['state'], DISABLED)
949        eq(d.custom_keyset_on.state(), ('selected',))
950        eq(d.customlist['state'], NORMAL)
951        eq(d.button_delete_custom_keys.state(), ())
952        d.set_keys_type = Func()
953
954    def test_get_new_keys(self):
955        eq = self.assertEqual
956        d = self.page
957        orig_getkeysdialog = configdialog.GetKeysDialog
958        gkd = configdialog.GetKeysDialog = Func(return_self=True)
959        gnkn = d.get_new_keys_name = Func()
960
961        d.button_new_keys.state(('!disabled',))
962        d.bindingslist.delete(0, 'end')
963        d.bindingslist.insert(0, 'copy - <Control-Shift-Key-C>')
964        d.bindingslist.selection_set(0)
965        d.bindingslist.selection_anchor(0)
966        d.keybinding.set('Key-a')
967        d.keyset_source.set(True)  # Default keyset.
968
969        # Default keyset; no change to binding.
970        gkd.result = ''
971        d.button_new_keys.invoke()
972        eq(d.bindingslist.get('anchor'), 'copy - <Control-Shift-Key-C>')
973        # Keybinding isn't changed when there isn't a change entered.
974        eq(d.keybinding.get(), 'Key-a')
975
976        # Default keyset; binding changed.
977        gkd.result = '<Key-F11>'
978        # No keyset name selected therefore binding not saved.
979        gnkn.result = ''
980        d.button_new_keys.invoke()
981        eq(gnkn.called, 1)
982        eq(d.bindingslist.get('anchor'), 'copy - <Control-Shift-Key-C>')
983        # Keyset name selected.
984        gnkn.result = 'My New Key Set'
985        d.button_new_keys.invoke()
986        eq(d.custom_name.get(), gnkn.result)
987        eq(d.bindingslist.get('anchor'), 'copy - <Key-F11>')
988        eq(d.keybinding.get(), '<Key-F11>')
989
990        # User keyset; binding changed.
991        d.keyset_source.set(False)  # Custom keyset.
992        gnkn.called = 0
993        gkd.result = '<Key-p>'
994        d.button_new_keys.invoke()
995        eq(gnkn.called, 0)
996        eq(d.bindingslist.get('anchor'), 'copy - <Key-p>')
997        eq(d.keybinding.get(), '<Key-p>')
998
999        del d.get_new_keys_name
1000        configdialog.GetKeysDialog = orig_getkeysdialog
1001
1002    def test_get_new_keys_name(self):
1003        orig_sectionname = configdialog.SectionName
1004        sn = configdialog.SectionName = Func(return_self=True)
1005        d = self.page
1006
1007        sn.result = 'New Keys'
1008        self.assertEqual(d.get_new_keys_name(''), 'New Keys')
1009
1010        configdialog.SectionName = orig_sectionname
1011
1012    def test_save_as_new_key_set(self):
1013        d = self.page
1014        gnkn = d.get_new_keys_name = Func()
1015        d.keyset_source.set(True)
1016
1017        # No name entered.
1018        gnkn.result = ''
1019        d.button_save_custom_keys.invoke()
1020
1021        # Name entered.
1022        gnkn.result = 'my new key set'
1023        gnkn.called = 0
1024        self.assertNotIn(gnkn.result, idleConf.userCfg['keys'])
1025        d.button_save_custom_keys.invoke()
1026        self.assertIn(gnkn.result, idleConf.userCfg['keys'])
1027
1028        del d.get_new_keys_name
1029
1030    def test_on_bindingslist_select(self):
1031        d = self.page
1032        b = d.bindingslist
1033        b.delete(0, 'end')
1034        b.insert(0, 'copy')
1035        b.insert(1, 'find')
1036        b.activate(0)
1037
1038        b.focus_force()
1039        b.see(1)
1040        b.update()
1041        x, y, dx, dy = b.bbox(1)
1042        x += dx // 2
1043        y += dy // 2
1044        b.event_generate('<Enter>', x=0, y=0)
1045        b.event_generate('<Motion>', x=x, y=y)
1046        b.event_generate('<Button-1>', x=x, y=y)
1047        b.event_generate('<ButtonRelease-1>', x=x, y=y)
1048        self.assertEqual(b.get('anchor'), 'find')
1049        self.assertEqual(d.button_new_keys.state(), ())
1050
1051    def test_create_new_key_set_and_save_new_key_set(self):
1052        eq = self.assertEqual
1053        d = self.page
1054
1055        # Use default as previously active keyset.
1056        d.keyset_source.set(True)
1057        d.builtin_name.set('IDLE Classic Windows')
1058        first_new = 'my new custom key set'
1059        second_new = 'my second custom keyset'
1060
1061        # No changes, so keysets are an exact copy.
1062        self.assertNotIn(first_new, idleConf.userCfg)
1063        d.create_new_key_set(first_new)
1064        eq(idleConf.GetSectionList('user', 'keys'), [first_new])
1065        eq(idleConf.GetKeySet('IDLE Classic Windows'),
1066           idleConf.GetKeySet(first_new))
1067        eq(d.custom_name.get(), first_new)
1068        self.assertFalse(d.keyset_source.get())  # Use custom set.
1069        eq(d.set_keys_type.called, 1)
1070
1071        # Test that changed keybindings are in new keyset.
1072        changes.add_option('keys', first_new, 'copy', '<Key-F11>')
1073        self.assertNotIn(second_new, idleConf.userCfg)
1074        d.create_new_key_set(second_new)
1075        eq(idleConf.GetSectionList('user', 'keys'), [first_new, second_new])
1076        self.assertNotEqual(idleConf.GetKeySet(first_new),
1077                            idleConf.GetKeySet(second_new))
1078        # Check that difference in keysets was in option `copy` from `changes`.
1079        idleConf.SetOption('keys', first_new, 'copy', '<Key-F11>')
1080        eq(idleConf.GetKeySet(first_new), idleConf.GetKeySet(second_new))
1081
1082    def test_load_keys_list(self):
1083        eq = self.assertEqual
1084        d = self.page
1085        gks = idleConf.GetKeySet = Func()
1086        del d.load_keys_list
1087        b = d.bindingslist
1088
1089        b.delete(0, 'end')
1090        b.insert(0, '<<find>>')
1091        b.insert(1, '<<help>>')
1092        gks.result = {'<<copy>>': ['<Control-Key-c>', '<Control-Key-C>'],
1093                      '<<force-open-completions>>': ['<Control-Key-space>'],
1094                      '<<spam>>': ['<Key-F11>']}
1095        changes.add_option('keys', 'my keys', 'spam', '<Shift-Key-a>')
1096        expected = ('copy - <Control-Key-c> <Control-Key-C>',
1097                    'force-open-completions - <Control-Key-space>',
1098                    'spam - <Shift-Key-a>')
1099
1100        # No current selection.
1101        d.load_keys_list('my keys')
1102        eq(b.get(0, 'end'), expected)
1103        eq(b.get('anchor'), '')
1104        eq(b.curselection(), ())
1105
1106        # Check selection.
1107        b.selection_set(1)
1108        b.selection_anchor(1)
1109        d.load_keys_list('my keys')
1110        eq(b.get(0, 'end'), expected)
1111        eq(b.get('anchor'), 'force-open-completions - <Control-Key-space>')
1112        eq(b.curselection(), (1, ))
1113
1114        # Change selection.
1115        b.selection_set(2)
1116        b.selection_anchor(2)
1117        d.load_keys_list('my keys')
1118        eq(b.get(0, 'end'), expected)
1119        eq(b.get('anchor'), 'spam - <Shift-Key-a>')
1120        eq(b.curselection(), (2, ))
1121        d.load_keys_list = Func()
1122
1123        del idleConf.GetKeySet
1124
1125    def test_delete_custom_keys(self):
1126        eq = self.assertEqual
1127        d = self.page
1128        d.button_delete_custom_keys.state(('!disabled',))
1129        yesno = d.askyesno = Func()
1130        dialog.deactivate_current_config = Func()
1131        dialog.activate_config_changes = Func()
1132
1133        keyset_name = 'spam key set'
1134        idleConf.userCfg['keys'].SetOption(keyset_name, 'name', 'value')
1135        keyspage[keyset_name] = {'option': 'True'}
1136
1137        keyset_name2 = 'other key set'
1138        idleConf.userCfg['keys'].SetOption(keyset_name2, 'name', 'value')
1139        keyspage[keyset_name2] = {'option': 'False'}
1140
1141        # Force custom keyset.
1142        d.custom_keyset_on.state(('!disabled',))
1143        d.custom_keyset_on.invoke()
1144        d.custom_name.set(keyset_name)
1145
1146        # Cancel deletion.
1147        yesno.result = False
1148        d.button_delete_custom_keys.invoke()
1149        eq(yesno.called, 1)
1150        eq(keyspage[keyset_name], {'option': 'True'})
1151        eq(idleConf.GetSectionList('user', 'keys'), [keyset_name, keyset_name2])
1152        eq(dialog.deactivate_current_config.called, 0)
1153        eq(dialog.activate_config_changes.called, 0)
1154        eq(d.set_keys_type.called, 0)
1155
1156        # Confirm deletion.
1157        yesno.result = True
1158        d.button_delete_custom_keys.invoke()
1159        eq(yesno.called, 2)
1160        self.assertNotIn(keyset_name, keyspage)
1161        eq(idleConf.GetSectionList('user', 'keys'), [keyset_name2])
1162        eq(d.custom_keyset_on.state(), ())
1163        eq(d.custom_name.get(), keyset_name2)
1164        eq(dialog.deactivate_current_config.called, 1)
1165        eq(dialog.activate_config_changes.called, 1)
1166        eq(d.set_keys_type.called, 1)
1167
1168        # Confirm deletion of second keyset - empties list.
1169        d.custom_name.set(keyset_name2)
1170        yesno.result = True
1171        d.button_delete_custom_keys.invoke()
1172        eq(yesno.called, 3)
1173        self.assertNotIn(keyset_name, keyspage)
1174        eq(idleConf.GetSectionList('user', 'keys'), [])
1175        eq(d.custom_keyset_on.state(), ('disabled',))
1176        eq(d.custom_name.get(), '- no custom keys -')
1177        eq(dialog.deactivate_current_config.called, 2)
1178        eq(dialog.activate_config_changes.called, 2)
1179        eq(d.set_keys_type.called, 2)
1180
1181        del dialog.activate_config_changes, dialog.deactivate_current_config
1182        del d.askyesno
1183
1184
1185class WinPageTest(unittest.TestCase):
1186    """Test that general tab widgets enable users to make changes.
1187
1188    Test that widget actions set vars, that var changes add
1189    options to changes.
1190    """
1191    @classmethod
1192    def setUpClass(cls):
1193        page = cls.page = dialog.winpage
1194        dialog.note.select(page)
1195        page.update()
1196
1197    def setUp(self):
1198        changes.clear()
1199
1200    def test_load_windows_cfg(self):
1201        # Set to wrong values, load, check right values.
1202        eq = self.assertEqual
1203        d = self.page
1204        d.startup_edit.set(1)
1205        d.win_width.set(1)
1206        d.win_height.set(1)
1207        d.load_windows_cfg()
1208        eq(d.startup_edit.get(), 0)
1209        eq(d.win_width.get(), '80')
1210        eq(d.win_height.get(), '40')
1211
1212    def test_startup(self):
1213        d = self.page
1214        d.startup_editor_on.invoke()
1215        self.assertEqual(mainpage,
1216                         {'General': {'editor-on-startup': '1'}})
1217        changes.clear()
1218        d.startup_shell_on.invoke()
1219        self.assertEqual(mainpage,
1220                         {'General': {'editor-on-startup': '0'}})
1221
1222    def test_editor_size(self):
1223        d = self.page
1224        d.win_height_int.delete(0, 'end')
1225        d.win_height_int.insert(0, '11')
1226        self.assertEqual(mainpage, {'EditorWindow': {'height': '11'}})
1227        changes.clear()
1228        d.win_width_int.delete(0, 'end')
1229        d.win_width_int.insert(0, '11')
1230        self.assertEqual(mainpage, {'EditorWindow': {'width': '11'}})
1231
1232    def test_indent_spaces(self):
1233        d = self.page
1234        d.indent_chooser.set(6)
1235        self.assertEqual(d.indent_spaces.get(), '6')
1236        self.assertEqual(mainpage, {'Indent': {'num-spaces': '6'}})
1237
1238    def test_cursor_blink(self):
1239        self.page.cursor_blink_bool.invoke()
1240        self.assertEqual(mainpage, {'EditorWindow': {'cursor-blink': 'False'}})
1241
1242    def test_autocomplete_wait(self):
1243        self.page.auto_wait_int.delete(0, 'end')
1244        self.page.auto_wait_int.insert(0, '11')
1245        self.assertEqual(extpage, {'AutoComplete': {'popupwait': '11'}})
1246
1247    def test_parenmatch(self):
1248        d = self.page
1249        eq = self.assertEqual
1250        d.paren_style_type['menu'].invoke(0)
1251        eq(extpage, {'ParenMatch': {'style': 'opener'}})
1252        changes.clear()
1253        d.paren_flash_time.delete(0, 'end')
1254        d.paren_flash_time.insert(0, '11')
1255        eq(extpage, {'ParenMatch': {'flash-delay': '11'}})
1256        changes.clear()
1257        d.bell_on.invoke()
1258        eq(extpage, {'ParenMatch': {'bell': 'False'}})
1259
1260    def test_paragraph(self):
1261        self.page.format_width_int.delete(0, 'end')
1262        self.page.format_width_int.insert(0, '11')
1263        self.assertEqual(extpage, {'FormatParagraph': {'max-width': '11'}})
1264
1265
1266class ShedPageTest(unittest.TestCase):
1267    """Test that shed tab widgets enable users to make changes.
1268
1269    Test that widget actions set vars, that var changes add
1270    options to changes.
1271    """
1272    @classmethod
1273    def setUpClass(cls):
1274        page = cls.page = dialog.shedpage
1275        dialog.note.select(page)
1276        page.update()
1277
1278    def setUp(self):
1279        changes.clear()
1280
1281    def test_load_shelled_cfg(self):
1282        # Set to wrong values, load, check right values.
1283        eq = self.assertEqual
1284        d = self.page
1285        d.autosave.set(1)
1286        d.load_shelled_cfg()
1287        eq(d.autosave.get(), 0)
1288
1289    def test_autosave(self):
1290        d = self.page
1291        d.save_auto_on.invoke()
1292        self.assertEqual(mainpage, {'General': {'autosave': '1'}})
1293        d.save_ask_on.invoke()
1294        self.assertEqual(mainpage, {'General': {'autosave': '0'}})
1295
1296    def test_context(self):
1297        self.page.context_int.delete(0, 'end')
1298        self.page.context_int.insert(0, '1')
1299        self.assertEqual(extpage, {'CodeContext': {'maxlines': '1'}})
1300
1301
1302#unittest.skip("Nothing here yet TODO")
1303class ExtPageTest(unittest.TestCase):
1304    """Test that the help source list works correctly."""
1305    @classmethod
1306    def setUpClass(cls):
1307        page = dialog.extpage
1308        dialog.note.select(page)
1309
1310
1311class HelpSourceTest(unittest.TestCase):
1312    """Test that the help source list works correctly."""
1313    @classmethod
1314    def setUpClass(cls):
1315        page = dialog.extpage
1316        dialog.note.select(page)
1317        frame = cls.frame = page.frame_help
1318        frame.set = frame.set_add_delete_state = Func()
1319        frame.upc = frame.update_help_changes = Func()
1320        frame.update()
1321
1322    @classmethod
1323    def tearDownClass(cls):
1324        frame = cls.frame
1325        del frame.set, frame.set_add_delete_state
1326        del frame.upc, frame.update_help_changes
1327        frame.helplist.delete(0, 'end')
1328        frame.user_helplist.clear()
1329
1330    def setUp(self):
1331        changes.clear()
1332
1333    def test_load_helplist(self):
1334        eq = self.assertEqual
1335        fr = self.frame
1336        fr.helplist.insert('end', 'bad')
1337        fr.user_helplist = ['bad', 'worse']
1338        idleConf.SetOption('main', 'HelpFiles', '1', 'name;file')
1339        fr.load_helplist()
1340        eq(fr.helplist.get(0, 'end'), ('name',))
1341        eq(fr.user_helplist, [('name', 'file', '1')])
1342
1343    def test_source_selected(self):
1344        fr = self.frame
1345        fr.set = fr.set_add_delete_state
1346        fr.upc = fr.update_help_changes
1347        helplist = fr.helplist
1348        dex = 'end'
1349        helplist.insert(dex, 'source')
1350        helplist.activate(dex)
1351
1352        helplist.focus_force()
1353        helplist.see(dex)
1354        helplist.update()
1355        x, y, dx, dy = helplist.bbox(dex)
1356        x += dx // 2
1357        y += dy // 2
1358        fr.set.called = fr.upc.called = 0
1359        helplist.event_generate('<Enter>', x=0, y=0)
1360        helplist.event_generate('<Motion>', x=x, y=y)
1361        helplist.event_generate('<Button-1>', x=x, y=y)
1362        helplist.event_generate('<ButtonRelease-1>', x=x, y=y)
1363        self.assertEqual(helplist.get('anchor'), 'source')
1364        self.assertTrue(fr.set.called)
1365        self.assertFalse(fr.upc.called)
1366
1367    def test_set_add_delete_state(self):
1368        # Call with 0 items, 1 unselected item, 1 selected item.
1369        eq = self.assertEqual
1370        fr = self.frame
1371        del fr.set_add_delete_state  # Unmask method.
1372        sad = fr.set_add_delete_state
1373        h = fr.helplist
1374
1375        h.delete(0, 'end')
1376        sad()
1377        eq(fr.button_helplist_edit.state(), ('disabled',))
1378        eq(fr.button_helplist_remove.state(), ('disabled',))
1379
1380        h.insert(0, 'source')
1381        sad()
1382        eq(fr.button_helplist_edit.state(), ('disabled',))
1383        eq(fr.button_helplist_remove.state(), ('disabled',))
1384
1385        h.selection_set(0)
1386        sad()
1387        eq(fr.button_helplist_edit.state(), ())
1388        eq(fr.button_helplist_remove.state(), ())
1389        fr.set_add_delete_state = Func()  # Mask method.
1390
1391    def test_helplist_item_add(self):
1392        # Call without and twice with HelpSource result.
1393        # Double call enables check on order.
1394        eq = self.assertEqual
1395        orig_helpsource = configdialog.HelpSource
1396        hs = configdialog.HelpSource = Func(return_self=True)
1397        fr = self.frame
1398        fr.helplist.delete(0, 'end')
1399        fr.user_helplist.clear()
1400        fr.set.called = fr.upc.called = 0
1401
1402        hs.result = ''
1403        fr.helplist_item_add()
1404        self.assertTrue(list(fr.helplist.get(0, 'end')) ==
1405                        fr.user_helplist == [])
1406        self.assertFalse(fr.upc.called)
1407
1408        hs.result = ('name1', 'file1')
1409        fr.helplist_item_add()
1410        hs.result = ('name2', 'file2')
1411        fr.helplist_item_add()
1412        eq(fr.helplist.get(0, 'end'), ('name1', 'name2'))
1413        eq(fr.user_helplist, [('name1', 'file1'), ('name2', 'file2')])
1414        eq(fr.upc.called, 2)
1415        self.assertFalse(fr.set.called)
1416
1417        configdialog.HelpSource = orig_helpsource
1418
1419    def test_helplist_item_edit(self):
1420        # Call without and with HelpSource change.
1421        eq = self.assertEqual
1422        orig_helpsource = configdialog.HelpSource
1423        hs = configdialog.HelpSource = Func(return_self=True)
1424        fr = self.frame
1425        fr.helplist.delete(0, 'end')
1426        fr.helplist.insert(0, 'name1')
1427        fr.helplist.selection_set(0)
1428        fr.helplist.selection_anchor(0)
1429        fr.user_helplist.clear()
1430        fr.user_helplist.append(('name1', 'file1'))
1431        fr.set.called = fr.upc.called = 0
1432
1433        hs.result = ''
1434        fr.helplist_item_edit()
1435        hs.result = ('name1', 'file1')
1436        fr.helplist_item_edit()
1437        eq(fr.helplist.get(0, 'end'), ('name1',))
1438        eq(fr.user_helplist, [('name1', 'file1')])
1439        self.assertFalse(fr.upc.called)
1440
1441        hs.result = ('name2', 'file2')
1442        fr.helplist_item_edit()
1443        eq(fr.helplist.get(0, 'end'), ('name2',))
1444        eq(fr.user_helplist, [('name2', 'file2')])
1445        self.assertTrue(fr.upc.called == fr.set.called == 1)
1446
1447        configdialog.HelpSource = orig_helpsource
1448
1449    def test_helplist_item_remove(self):
1450        eq = self.assertEqual
1451        fr = self.frame
1452        fr.helplist.delete(0, 'end')
1453        fr.helplist.insert(0, 'name1')
1454        fr.helplist.selection_set(0)
1455        fr.helplist.selection_anchor(0)
1456        fr.user_helplist.clear()
1457        fr.user_helplist.append(('name1', 'file1'))
1458        fr.set.called = fr.upc.called = 0
1459
1460        fr.helplist_item_remove()
1461        eq(fr.helplist.get(0, 'end'), ())
1462        eq(fr.user_helplist, [])
1463        self.assertTrue(fr.upc.called == fr.set.called == 1)
1464
1465    def test_update_help_changes(self):
1466        fr = self.frame
1467        del fr.update_help_changes
1468        fr.user_helplist.clear()
1469        fr.user_helplist.append(('name1', 'file1'))
1470        fr.user_helplist.append(('name2', 'file2'))
1471
1472        fr.update_help_changes()
1473        self.assertEqual(mainpage['HelpFiles'],
1474                         {'1': 'name1;file1', '2': 'name2;file2'})
1475        fr.update_help_changes = Func()
1476
1477
1478class VarTraceTest(unittest.TestCase):
1479
1480    @classmethod
1481    def setUpClass(cls):
1482        cls.tracers = configdialog.VarTrace()
1483        cls.iv = IntVar(root)
1484        cls.bv = BooleanVar(root)
1485
1486    @classmethod
1487    def tearDownClass(cls):
1488        del cls.tracers, cls.iv, cls.bv
1489
1490    def setUp(self):
1491        self.tracers.clear()
1492        self.called = 0
1493
1494    def var_changed_increment(self, *params):
1495        self.called += 13
1496
1497    def var_changed_boolean(self, *params):
1498        pass
1499
1500    def test_init(self):
1501        tr = self.tracers
1502        tr.__init__()
1503        self.assertEqual(tr.untraced, [])
1504        self.assertEqual(tr.traced, [])
1505
1506    def test_clear(self):
1507        tr = self.tracers
1508        tr.untraced.append(0)
1509        tr.traced.append(1)
1510        tr.clear()
1511        self.assertEqual(tr.untraced, [])
1512        self.assertEqual(tr.traced, [])
1513
1514    def test_add(self):
1515        tr = self.tracers
1516        func = Func()
1517        cb = tr.make_callback = mock.Mock(return_value=func)
1518
1519        iv = tr.add(self.iv, self.var_changed_increment)
1520        self.assertIs(iv, self.iv)
1521        bv = tr.add(self.bv, self.var_changed_boolean)
1522        self.assertIs(bv, self.bv)
1523
1524        sv = StringVar(root)
1525        sv2 = tr.add(sv, ('main', 'section', 'option'))
1526        self.assertIs(sv2, sv)
1527        cb.assert_called_once()
1528        cb.assert_called_with(sv, ('main', 'section', 'option'))
1529
1530        expected = [(iv, self.var_changed_increment),
1531                    (bv, self.var_changed_boolean),
1532                    (sv, func)]
1533        self.assertEqual(tr.traced, [])
1534        self.assertEqual(tr.untraced, expected)
1535
1536        del tr.make_callback
1537
1538    def test_make_callback(self):
1539        cb = self.tracers.make_callback(self.iv, ('main', 'section', 'option'))
1540        self.assertTrue(callable(cb))
1541        self.iv.set(42)
1542        # Not attached, so set didn't invoke the callback.
1543        self.assertNotIn('section', changes['main'])
1544        # Invoke callback manually.
1545        cb()
1546        self.assertIn('section', changes['main'])
1547        self.assertEqual(changes['main']['section']['option'], '42')
1548        changes.clear()
1549
1550    def test_attach_detach(self):
1551        tr = self.tracers
1552        iv = tr.add(self.iv, self.var_changed_increment)
1553        bv = tr.add(self.bv, self.var_changed_boolean)
1554        expected = [(iv, self.var_changed_increment),
1555                    (bv, self.var_changed_boolean)]
1556
1557        # Attach callbacks and test call increment.
1558        tr.attach()
1559        self.assertEqual(tr.untraced, [])
1560        self.assertCountEqual(tr.traced, expected)
1561        iv.set(1)
1562        self.assertEqual(iv.get(), 1)
1563        self.assertEqual(self.called, 13)
1564
1565        # Check that only one callback is attached to a variable.
1566        # If more than one callback were attached, then var_changed_increment
1567        # would be called twice and the counter would be 2.
1568        self.called = 0
1569        tr.attach()
1570        iv.set(1)
1571        self.assertEqual(self.called, 13)
1572
1573        # Detach callbacks.
1574        self.called = 0
1575        tr.detach()
1576        self.assertEqual(tr.traced, [])
1577        self.assertCountEqual(tr.untraced, expected)
1578        iv.set(1)
1579        self.assertEqual(self.called, 0)
1580
1581
1582if __name__ == '__main__':
1583    unittest.main(verbosity=2)
1584