1 """Test configdialog, coverage 94%.
2
3 Half the class creates dialog, half works with user customizations.
4 """
5 from idlelib import configdialog
6 from test.support import requires
7 requires('gui')
8 import unittest
9 from unittest import mock
10 from idlelib.idle_test.mock_idle import Func
11 from tkinter import (Tk, StringVar, IntVar, BooleanVar, DISABLED, NORMAL)
12 from idlelib import config
13 from 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.
18 usercfg = idleConf.userCfg
19 testcfg = {
20 'main': config.IdleUserConfParser(''),
21 'highlight': config.IdleUserConfParser(''),
22 'keys': config.IdleUserConfParser(''),
23 'extensions': config.IdleUserConfParser(''),
24 }
25
26 root = None
27 dialog = None
28 mainpage = changes['main']
29 highpage = changes['highlight']
30 keyspage = changes['keys']
31 extpage = changes['extensions']
32
33
34 def 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
42 def 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
53 class ESC[4;38;5;81mConfigDialogTest(ESC[4;38;5;149munittestESC[4;38;5;149m.ESC[4;38;5;149mTestCase):
54
55 def test_deactivate_current_config(self):
56 pass
57
58 def activate_config_changes(self):
59 pass
60
61
62 class ESC[4;38;5;81mButtonTest(ESC[4;38;5;149munittestESC[4;38;5;149m.ESC[4;38;5;149mTestCase):
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
105 class ESC[4;38;5;81mFontPageTest(ESC[4;38;5;149munittestESC[4;38;5;149m.ESC[4;38;5;149mTestCase):
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
263 class ESC[4;38;5;81mHighPageTest(ESC[4;38;5;149munittestESC[4;38;5;149m.ESC[4;38;5;149mTestCase):
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
774 class ESC[4;38;5;81mKeysPageTest(ESC[4;38;5;149munittestESC[4;38;5;149m.ESC[4;38;5;149mTestCase):
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.GetKeysWindow
958 gkd = configdialog.GetKeysWindow = 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.GetKeysWindow = 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
1185 class ESC[4;38;5;81mWinPageTest(ESC[4;38;5;149munittestESC[4;38;5;149m.ESC[4;38;5;149mTestCase):
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
1266 class ESC[4;38;5;81mShedPageTest(ESC[4;38;5;149munittestESC[4;38;5;149m.ESC[4;38;5;149mTestCase):
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")
1303 class ESC[4;38;5;81mExtPageTest(ESC[4;38;5;149munittestESC[4;38;5;149m.ESC[4;38;5;149mTestCase):
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
1311 class ESC[4;38;5;81mHelpSourceTest(ESC[4;38;5;149munittestESC[4;38;5;149m.ESC[4;38;5;149mTestCase):
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
1478 class ESC[4;38;5;81mVarTraceTest(ESC[4;38;5;149munittestESC[4;38;5;149m.ESC[4;38;5;149mTestCase):
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
1582 if __name__ == '__main__':
1583 unittest.main(verbosity=2)