Coverage for idle_test/test_configdialog.py: 1%
1152 statements
« prev ^ index » next coverage.py v7.2.5, created at 2023-05-11 13:22 -0700
« prev ^ index » next coverage.py v7.2.5, created at 2023-05-11 13:22 -0700
1"""Test configdialog, coverage 94%.
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
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}
26root = None
27dialog = None
28mainpage = changes['main']
29highpage = changes['highlight']
30keyspage = changes['keys']
31extpage = changes['extensions']
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)
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
53class ConfigDialogTest(unittest.TestCase):
55 def test_deactivate_current_config(self):
56 pass
58 def activate_config_changes(self):
59 pass
62class ButtonTest(unittest.TestCase):
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
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
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
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'))
105class FontPageTest(unittest.TestCase):
106 """Test that font widgets enable users to make font changes.
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()
119 @classmethod
120 def tearDownClass(cls):
121 del cls.page.set_samples # Unmask instance method.
123 def setUp(self):
124 changes.clear()
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()
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')
151 # Test Down key.
152 fontlist.focus_force()
153 fontlist.update()
154 fontlist.event_generate('<Key-Down>')
155 fontlist.event_generate('<KeyRelease-Down>')
157 down_font = fontlist.get('active')
158 self.assertNotEqual(down_font, font)
159 self.assertIn(d.font_name.get(), down_font.lower())
161 # Test Up key.
162 fontlist.focus_force()
163 fontlist.update()
164 fontlist.event_generate('<Key-Up>')
165 fontlist.event_generate('<KeyRelease-Up>')
167 up_font = fontlist.get('active')
168 self.assertEqual(up_font, font)
169 self.assertIn(d.font_name.get(), up_font.lower())
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)
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)
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())
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')
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())
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.
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
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()
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()
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)
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')}
255 # Test set_samples.
256 d.set_samples()
257 self.assertTrue(d.font_sample == d.highlight_sample == expected)
259 d.font_sample, d.highlight_sample = orig_samples
260 d.set_samples = Func() # Re-mask for other tests.
263class HighPageTest(unittest.TestCase):
264 """Test that highlight tab widgets enable users to make changes.
266 Test that widget actions set vars, that var changes add
267 options to changes and that themes work correctly.
268 """
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()
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
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
298 def test_load_theme_cfg(self):
299 tracers.detach()
300 d = self.page
301 eq = self.assertEqual
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)
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)
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)
336 del idleConf.CurrentTheme
337 tracers.attach()
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()
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
360 def test_builtin_name(self):
361 eq = self.assertEqual
362 d = self.page
363 item_list = ['IDLE Classic', 'IDLE Dark', 'IDLE New']
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)
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)
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)
388 def test_custom_name(self):
389 d = self.page
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)
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)
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
410 def test_highlight_target_list_mouse(self):
411 # Set highlight_target through targetlist.
412 eq = self.assertEqual
413 d = self.page
415 d.targetlist.SetMenu(['a', 'b', 'c'], 'c')
416 eq(d.highlight_target.get(), 'c')
417 eq(d.set_highlight_target.called, 1)
419 def test_highlight_target_text_mouse(self):
420 # Set highlight_target through clicking highlight_sample.
421 eq = self.assertEqual
422 d = self.page
424 elem = {}
425 count = 0
426 hs = d.highlight_sample
427 hs.focus_force()
428 hs.see(1.0)
429 hs.update_idletasks()
431 def tag_to_element(elem):
432 for element, tag in d.theme_elements.items():
433 elem[tag[0]] = element
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)
444 # Flip theme_elements to make the tag the key.
445 tag_to_element(elem)
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)
456 def test_highlight_sample_double_click(self):
457 # Test double click on highlight_sample.
458 eq = self.assertEqual
459 d = self.page
461 hs = d.highlight_sample
462 hs.focus_force()
463 hs.see(1.0)
464 hs.update_idletasks()
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)
474 eq(hs.tag_ranges('sel'), ())
476 def test_highlight_sample_b1_motion(self):
477 # Test button motion on highlight_sample.
478 eq = self.assertEqual
479 d = self.page
481 hs = d.highlight_sample
482 hs.focus_force()
483 hs.see(1.0)
484 hs.update_idletasks()
486 x, y, dx, dy, offset = hs.dlineinfo('1.0')
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)
496 eq(hs.tag_ranges('sel'), ())
498 def test_set_theme_type(self):
499 eq = self.assertEqual
500 d = self.page
501 del d.set_theme_type
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',))
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()
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()
526 d.highlight_target.set('Editor Breakpoint')
527 d.color.set('#ffffff')
529 # Nothing selected.
530 chooser.result = (None, None)
531 d.button_set_color.invoke()
532 eq(d.color.get(), '#ffffff')
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')
539 # Select different color.
540 chooser.result = ((222.8671875, 0.0, 0.0), '#de0000')
542 # Default theme.
543 d.color.set('#ffffff')
544 d.theme_source.set(True)
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')
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')
563 del d.get_new_theme_name
564 configdialog.colorchooser.askcolor = orig_chooser
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)
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}})
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
584 sn.result = 'New Theme'
585 self.assertEqual(d.get_new_theme_name(''), 'New Theme')
587 configdialog.SectionName = orig_sectionname
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)
594 # No name entered.
595 gntn.result = ''
596 d.button_save_custom.invoke()
597 self.assertNotIn(gntn.result, idleConf.userCfg['highlight'])
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'])
606 del d.get_new_theme_name
608 def test_create_new_and_save_new(self):
609 eq = self.assertEqual
610 d = self.page
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'
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)
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))
640 def test_set_highlight_target(self):
641 eq = self.assertEqual
642 d = self.page
643 del d.set_highlight_target
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)
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)
659 d.set_highlight_target = Func()
661 def test_set_color_sample_binding(self):
662 d = self.page
663 scs = d.set_color_sample
665 d.fg_on.invoke()
666 self.assertEqual(scs.called, 1)
668 d.bg_on.invoke()
669 self.assertEqual(scs.called, 2)
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()
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
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
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)
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)
712 page.paint_theme_sample = Func()
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()
722 theme_name = 'spam theme'
723 idleConf.userCfg['highlight'].SetOption(theme_name, 'name', 'value')
724 highpage[theme_name] = {'option': 'True'}
726 theme_name2 = 'other theme'
727 idleConf.userCfg['highlight'].SetOption(theme_name2, 'name', 'value')
728 highpage[theme_name2] = {'option': 'False'}
730 # Force custom theme.
731 d.custom_theme_on.state(('!disabled',))
732 d.custom_theme_on.invoke()
733 d.custom_name.set(theme_name)
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)
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)
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)
770 del dialog.activate_config_changes, dialog.deactivate_current_config
771 del d.askyesno
774class KeysPageTest(unittest.TestCase):
775 """Test that keys tab widgets enable users to make changes.
777 Test that widget actions set vars, that var changes add
778 options to changes and that key sets works correctly.
779 """
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()
788 @classmethod
789 def tearDownClass(cls):
790 page = cls.page
791 del page.set_keys_type, page.load_keys_list
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
803 def test_load_key_cfg(self):
804 tracers.detach()
805 d = self.page
806 eq = self.assertEqual
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', ))
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', ))
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', ))
842 del idleConf.CurrentKeys, idleConf.default_keys
843 tracers.attach()
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()
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
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']
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', ))
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', ))
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', ))
898 def test_custom_name(self):
899 d = self.page
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)
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)
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>'}})
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>'}})
933 def test_set_keys_type(self):
934 eq = self.assertEqual
935 d = self.page
936 del d.set_keys_type
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',))
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()
954 def test_get_new_keys(self):
955 eq = self.assertEqual
956 d = self.page
957 orig_getkeysdialog = configdialog.GetKeysWindow
958 gkd = configdialog.GetKeysWindow = Func(return_self=True)
959 gnkn = d.get_new_keys_name = Func()
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.
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')
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>')
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>')
999 del d.get_new_keys_name
1000 configdialog.GetKeysWindow = orig_getkeysdialog
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
1007 sn.result = 'New Keys'
1008 self.assertEqual(d.get_new_keys_name(''), 'New Keys')
1010 configdialog.SectionName = orig_sectionname
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)
1017 # No name entered.
1018 gnkn.result = ''
1019 d.button_save_custom_keys.invoke()
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'])
1028 del d.get_new_keys_name
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)
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(), ())
1051 def test_create_new_key_set_and_save_new_key_set(self):
1052 eq = self.assertEqual
1053 d = self.page
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'
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)
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))
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
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>')
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(), ())
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, ))
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()
1123 del idleConf.GetKeySet
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()
1133 keyset_name = 'spam key set'
1134 idleConf.userCfg['keys'].SetOption(keyset_name, 'name', 'value')
1135 keyspage[keyset_name] = {'option': 'True'}
1137 keyset_name2 = 'other key set'
1138 idleConf.userCfg['keys'].SetOption(keyset_name2, 'name', 'value')
1139 keyspage[keyset_name2] = {'option': 'False'}
1141 # Force custom keyset.
1142 d.custom_keyset_on.state(('!disabled',))
1143 d.custom_keyset_on.invoke()
1144 d.custom_name.set(keyset_name)
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)
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)
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)
1181 del dialog.activate_config_changes, dialog.deactivate_current_config
1182 del d.askyesno
1185class WinPageTest(unittest.TestCase):
1186 """Test that general tab widgets enable users to make changes.
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()
1197 def setUp(self):
1198 changes.clear()
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')
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'}})
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'}})
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'}})
1238 def test_cursor_blink(self):
1239 self.page.cursor_blink_bool.invoke()
1240 self.assertEqual(mainpage, {'EditorWindow': {'cursor-blink': 'False'}})
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'}})
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'}})
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'}})
1266class ShedPageTest(unittest.TestCase):
1267 """Test that shed tab widgets enable users to make changes.
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()
1278 def setUp(self):
1279 changes.clear()
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)
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'}})
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'}})
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)
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()
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()
1330 def setUp(self):
1331 changes.clear()
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')])
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)
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)
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
1375 h.delete(0, 'end')
1376 sad()
1377 eq(fr.button_helplist_edit.state(), ('disabled',))
1378 eq(fr.button_helplist_remove.state(), ('disabled',))
1380 h.insert(0, 'source')
1381 sad()
1382 eq(fr.button_helplist_edit.state(), ('disabled',))
1383 eq(fr.button_helplist_remove.state(), ('disabled',))
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.
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
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)
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)
1417 configdialog.HelpSource = orig_helpsource
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
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)
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)
1447 configdialog.HelpSource = orig_helpsource
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
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)
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'))
1472 fr.update_help_changes()
1473 self.assertEqual(mainpage['HelpFiles'],
1474 {'1': 'name1;file1', '2': 'name2;file2'})
1475 fr.update_help_changes = Func()
1478class VarTraceTest(unittest.TestCase):
1480 @classmethod
1481 def setUpClass(cls):
1482 cls.tracers = configdialog.VarTrace()
1483 cls.iv = IntVar(root)
1484 cls.bv = BooleanVar(root)
1486 @classmethod
1487 def tearDownClass(cls):
1488 del cls.tracers, cls.iv, cls.bv
1490 def setUp(self):
1491 self.tracers.clear()
1492 self.called = 0
1494 def var_changed_increment(self, *params):
1495 self.called += 13
1497 def var_changed_boolean(self, *params):
1498 pass
1500 def test_init(self):
1501 tr = self.tracers
1502 tr.__init__()
1503 self.assertEqual(tr.untraced, [])
1504 self.assertEqual(tr.traced, [])
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, [])
1514 def test_add(self):
1515 tr = self.tracers
1516 func = Func()
1517 cb = tr.make_callback = mock.Mock(return_value=func)
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)
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'))
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)
1536 del tr.make_callback
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()
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)]
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)
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)
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)
1582if __name__ == '__main__':
1583 unittest.main(verbosity=2)