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