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