1 """Test configdialog, coverage 94%. 2 3 Half the class creates dialog, half works with user customizations. 4 """ 5 from idlelib import configdialog 6 from test.support import requires 7 requires('gui') 8 import unittest 9 from unittest import mock 10 from idlelib.idle_test.mock_idle import Func 11 from tkinter import Tk, StringVar, IntVar, BooleanVar, DISABLED, NORMAL 12 from idlelib import config 13 from idlelib.configdialog import idleConf, changes, tracers 14 15 # Tests should not depend on fortuitous user configurations. 16 # They must not affect actual user .cfg files. 17 # Use solution from test_config: empty parsers with no filename. 18 usercfg = idleConf.userCfg 19 testcfg = { 20 'main': config.IdleUserConfParser(''), 21 'highlight': config.IdleUserConfParser(''), 22 'keys': config.IdleUserConfParser(''), 23 'extensions': config.IdleUserConfParser(''), 24 } 25 26 root = None 27 dialog = None 28 mainpage = changes['main'] 29 highpage = changes['highlight'] 30 keyspage = changes['keys'] 31 extpage = changes['extensions'] 32 33 def 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 40 def 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 51 class 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 209 class 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 230 class 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 d = self.page 610 del d.paint_theme_sample 611 hs_tag = d.highlight_sample.tag_cget 612 gh = idleConf.GetHighlight 613 fg = 'foreground' 614 bg = 'background' 615 616 # Create custom theme based on IDLE Dark. 617 d.theme_source.set(True) 618 d.builtin_name.set('IDLE Dark') 619 theme = 'IDLE Test' 620 d.create_new(theme) 621 d.set_color_sample.called = 0 622 623 # Base theme with nothing in `changes`. 624 d.paint_theme_sample() 625 eq(hs_tag('break', fg), gh(theme, 'break', fgBg='fg')) 626 eq(hs_tag('cursor', bg), gh(theme, 'normal', fgBg='bg')) 627 self.assertNotEqual(hs_tag('console', fg), 'blue') 628 self.assertNotEqual(hs_tag('console', bg), 'yellow') 629 eq(d.set_color_sample.called, 1) 630 631 # Apply changes. 632 changes.add_option('highlight', theme, 'console-foreground', 'blue') 633 changes.add_option('highlight', theme, 'console-background', 'yellow') 634 d.paint_theme_sample() 635 636 eq(hs_tag('break', fg), gh(theme, 'break', fgBg='fg')) 637 eq(hs_tag('cursor', bg), gh(theme, 'normal', fgBg='bg')) 638 eq(hs_tag('console', fg), 'blue') 639 eq(hs_tag('console', bg), 'yellow') 640 eq(d.set_color_sample.called, 2) 641 642 d.paint_theme_sample = Func() 643 644 def test_delete_custom(self): 645 eq = self.assertEqual 646 d = self.page 647 d.button_delete_custom.state(('!disabled',)) 648 yesno = d.askyesno = Func() 649 dialog.deactivate_current_config = Func() 650 dialog.activate_config_changes = Func() 651 652 theme_name = 'spam theme' 653 idleConf.userCfg['highlight'].SetOption(theme_name, 'name', 'value') 654 highpage[theme_name] = {'option': 'True'} 655 656 # Force custom theme. 657 d.theme_source.set(False) 658 d.custom_name.set(theme_name) 659 660 # Cancel deletion. 661 yesno.result = False 662 d.button_delete_custom.invoke() 663 eq(yesno.called, 1) 664 eq(highpage[theme_name], {'option': 'True'}) 665 eq(idleConf.GetSectionList('user', 'highlight'), ['spam theme']) 666 eq(dialog.deactivate_current_config.called, 0) 667 eq(dialog.activate_config_changes.called, 0) 668 eq(d.set_theme_type.called, 0) 669 670 # Confirm deletion. 671 yesno.result = True 672 d.button_delete_custom.invoke() 673 eq(yesno.called, 2) 674 self.assertNotIn(theme_name, highpage) 675 eq(idleConf.GetSectionList('user', 'highlight'), []) 676 eq(d.custom_theme_on.state(), ('disabled',)) 677 eq(d.custom_name.get(), '- no custom themes -') 678 eq(dialog.deactivate_current_config.called, 1) 679 eq(dialog.activate_config_changes.called, 1) 680 eq(d.set_theme_type.called, 1) 681 682 del dialog.activate_config_changes, dialog.deactivate_current_config 683 del d.askyesno 684 685 686 class KeysPageTest(unittest.TestCase): 687 """Test that keys tab widgets enable users to make changes. 688 689 Test that widget actions set vars, that var changes add 690 options to changes and that key sets works correctly. 691 """ 692 693 @classmethod 694 def setUpClass(cls): 695 page = cls.page = dialog.keyspage 696 dialog.note.select(page) 697 page.set_keys_type = Func() 698 page.load_keys_list = Func() 699 700 @classmethod 701 def tearDownClass(cls): 702 page = cls.page 703 del page.set_keys_type, page.load_keys_list 704 705 def setUp(self): 706 d = self.page 707 # The following is needed for test_load_key_cfg, _delete_custom_keys. 708 # This may indicate a defect in some test or function. 709 for section in idleConf.GetSectionList('user', 'keys'): 710 idleConf.userCfg['keys'].remove_section(section) 711 changes.clear() 712 d.set_keys_type.called = 0 713 d.load_keys_list.called = 0 714 715 def test_load_key_cfg(self): 716 tracers.detach() 717 d = self.page 718 eq = self.assertEqual 719 720 # Use builtin keyset with no user keysets created. 721 idleConf.CurrentKeys = mock.Mock(return_value='IDLE Classic OSX') 722 d.load_key_cfg() 723 self.assertTrue(d.keyset_source.get()) 724 # builtinlist sets variable builtin_name to the CurrentKeys default. 725 eq(d.builtin_name.get(), 'IDLE Classic OSX') 726 eq(d.custom_name.get(), '- no custom keys -') 727 eq(d.custom_keyset_on.state(), ('disabled',)) 728 eq(d.set_keys_type.called, 1) 729 eq(d.load_keys_list.called, 1) 730 eq(d.load_keys_list.args, ('IDLE Classic OSX', )) 731 732 # Builtin keyset with non-empty user keyset list. 733 idleConf.SetOption('keys', 'test1', 'option', 'value') 734 idleConf.SetOption('keys', 'test2', 'option2', 'value2') 735 d.load_key_cfg() 736 eq(d.builtin_name.get(), 'IDLE Classic OSX') 737 eq(d.custom_name.get(), 'test1') 738 eq(d.set_keys_type.called, 2) 739 eq(d.load_keys_list.called, 2) 740 eq(d.load_keys_list.args, ('IDLE Classic OSX', )) 741 742 # Use custom keyset. 743 idleConf.CurrentKeys = mock.Mock(return_value='test2') 744 idleConf.default_keys = mock.Mock(return_value='IDLE Modern Unix') 745 idleConf.SetOption('main', 'Keys', 'default', '0') 746 d.load_key_cfg() 747 self.assertFalse(d.keyset_source.get()) 748 eq(d.builtin_name.get(), 'IDLE Modern Unix') 749 eq(d.custom_name.get(), 'test2') 750 eq(d.set_keys_type.called, 3) 751 eq(d.load_keys_list.called, 3) 752 eq(d.load_keys_list.args, ('test2', )) 753 754 del idleConf.CurrentKeys, idleConf.default_keys 755 tracers.attach() 756 757 def test_keyset_source(self): 758 eq = self.assertEqual 759 d = self.page 760 # Test these separately. 761 d.var_changed_builtin_name = Func() 762 d.var_changed_custom_name = Func() 763 # Builtin selected. 764 d.builtin_keyset_on.invoke() 765 eq(mainpage, {'Keys': {'default': 'True'}}) 766 eq(d.var_changed_builtin_name.called, 1) 767 eq(d.var_changed_custom_name.called, 0) 768 changes.clear() 769 770 # Custom selected. 771 d.custom_keyset_on.state(('!disabled',)) 772 d.custom_keyset_on.invoke() 773 self.assertEqual(mainpage, {'Keys': {'default': 'False'}}) 774 eq(d.var_changed_builtin_name.called, 1) 775 eq(d.var_changed_custom_name.called, 1) 776 del d.var_changed_builtin_name, d.var_changed_custom_name 777 778 def test_builtin_name(self): 779 eq = self.assertEqual 780 d = self.page 781 idleConf.userCfg['main'].remove_section('Keys') 782 item_list = ['IDLE Classic Windows', 'IDLE Classic OSX', 783 'IDLE Modern UNIX'] 784 785 # Not in old_keys, defaults name to first item. 786 d.builtinlist.SetMenu(item_list, 'IDLE Modern UNIX') 787 eq(mainpage, {'Keys': {'name': 'IDLE Classic Windows', 788 'name2': 'IDLE Modern UNIX'}}) 789 eq(d.keys_message['text'], 'New key set, see Help') 790 eq(d.load_keys_list.called, 1) 791 eq(d.load_keys_list.args, ('IDLE Modern UNIX', )) 792 793 # Not in old keys - uses name2. 794 changes.clear() 795 idleConf.SetOption('main', 'Keys', 'name', 'IDLE Classic Unix') 796 d.builtinlist.SetMenu(item_list, 'IDLE Modern UNIX') 797 eq(mainpage, {'Keys': {'name2': 'IDLE Modern UNIX'}}) 798 eq(d.keys_message['text'], 'New key set, see Help') 799 eq(d.load_keys_list.called, 2) 800 eq(d.load_keys_list.args, ('IDLE Modern UNIX', )) 801 802 # Builtin name in old_keys. 803 changes.clear() 804 d.builtinlist.SetMenu(item_list, 'IDLE Classic OSX') 805 eq(mainpage, {'Keys': {'name': 'IDLE Classic OSX', 'name2': ''}}) 806 eq(d.keys_message['text'], '') 807 eq(d.load_keys_list.called, 3) 808 eq(d.load_keys_list.args, ('IDLE Classic OSX', )) 809 810 def test_custom_name(self): 811 d = self.page 812 813 # If no selections, doesn't get added. 814 d.customlist.SetMenu([], '- no custom keys -') 815 self.assertNotIn('Keys', mainpage) 816 self.assertEqual(d.load_keys_list.called, 0) 817 818 # Custom name selected. 819 changes.clear() 820 d.customlist.SetMenu(['a', 'b', 'c'], 'c') 821 self.assertEqual(mainpage, {'Keys': {'name': 'c'}}) 822 self.assertEqual(d.load_keys_list.called, 1) 823 824 def test_keybinding(self): 825 idleConf.SetOption('extensions', 'ZzDummy', 'enable', 'True') 826 d = self.page 827 d.custom_name.set('my custom keys') 828 d.bindingslist.delete(0, 'end') 829 d.bindingslist.insert(0, 'copy') 830 d.bindingslist.insert(1, 'z-in') 831 d.bindingslist.selection_set(0) 832 d.bindingslist.selection_anchor(0) 833 # Core binding - adds to keys. 834 d.keybinding.set('<Key-F11>') 835 self.assertEqual(keyspage, 836 {'my custom keys': {'copy': '<Key-F11>'}}) 837 838 # Not a core binding - adds to extensions. 839 d.bindingslist.selection_set(1) 840 d.bindingslist.selection_anchor(1) 841 d.keybinding.set('<Key-F11>') 842 self.assertEqual(extpage, 843 {'ZzDummy_cfgBindings': {'z-in': '<Key-F11>'}}) 844 845 def test_set_keys_type(self): 846 eq = self.assertEqual 847 d = self.page 848 del d.set_keys_type 849 850 # Builtin keyset selected. 851 d.keyset_source.set(True) 852 d.set_keys_type() 853 eq(d.builtinlist['state'], NORMAL) 854 eq(d.customlist['state'], DISABLED) 855 eq(d.button_delete_custom_keys.state(), ('disabled',)) 856 857 # Custom keyset selected. 858 d.keyset_source.set(False) 859 d.set_keys_type() 860 eq(d.builtinlist['state'], DISABLED) 861 eq(d.custom_keyset_on.state(), ('selected',)) 862 eq(d.customlist['state'], NORMAL) 863 eq(d.button_delete_custom_keys.state(), ()) 864 d.set_keys_type = Func() 865 866 def test_get_new_keys(self): 867 eq = self.assertEqual 868 d = self.page 869 orig_getkeysdialog = configdialog.GetKeysDialog 870 gkd = configdialog.GetKeysDialog = Func(return_self=True) 871 gnkn = d.get_new_keys_name = Func() 872 873 d.button_new_keys.state(('!disabled',)) 874 d.bindingslist.delete(0, 'end') 875 d.bindingslist.insert(0, 'copy - <Control-Shift-Key-C>') 876 d.bindingslist.selection_set(0) 877 d.bindingslist.selection_anchor(0) 878 d.keybinding.set('Key-a') 879 d.keyset_source.set(True) # Default keyset. 880 881 # Default keyset; no change to binding. 882 gkd.result = '' 883 d.button_new_keys.invoke() 884 eq(d.bindingslist.get('anchor'), 'copy - <Control-Shift-Key-C>') 885 # Keybinding isn't changed when there isn't a change entered. 886 eq(d.keybinding.get(), 'Key-a') 887 888 # Default keyset; binding changed. 889 gkd.result = '<Key-F11>' 890 # No keyset name selected therefore binding not saved. 891 gnkn.result = '' 892 d.button_new_keys.invoke() 893 eq(gnkn.called, 1) 894 eq(d.bindingslist.get('anchor'), 'copy - <Control-Shift-Key-C>') 895 # Keyset name selected. 896 gnkn.result = 'My New Key Set' 897 d.button_new_keys.invoke() 898 eq(d.custom_name.get(), gnkn.result) 899 eq(d.bindingslist.get('anchor'), 'copy - <Key-F11>') 900 eq(d.keybinding.get(), '<Key-F11>') 901 902 # User keyset; binding changed. 903 d.keyset_source.set(False) # Custom keyset. 904 gnkn.called = 0 905 gkd.result = '<Key-p>' 906 d.button_new_keys.invoke() 907 eq(gnkn.called, 0) 908 eq(d.bindingslist.get('anchor'), 'copy - <Key-p>') 909 eq(d.keybinding.get(), '<Key-p>') 910 911 del d.get_new_keys_name 912 configdialog.GetKeysDialog = orig_getkeysdialog 913 914 def test_get_new_keys_name(self): 915 orig_sectionname = configdialog.SectionName 916 sn = configdialog.SectionName = Func(return_self=True) 917 d = self.page 918 919 sn.result = 'New Keys' 920 self.assertEqual(d.get_new_keys_name(''), 'New Keys') 921 922 configdialog.SectionName = orig_sectionname 923 924 def test_save_as_new_key_set(self): 925 d = self.page 926 gnkn = d.get_new_keys_name = Func() 927 d.keyset_source.set(True) 928 929 # No name entered. 930 gnkn.result = '' 931 d.button_save_custom_keys.invoke() 932 933 # Name entered. 934 gnkn.result = 'my new key set' 935 gnkn.called = 0 936 self.assertNotIn(gnkn.result, idleConf.userCfg['keys']) 937 d.button_save_custom_keys.invoke() 938 self.assertIn(gnkn.result, idleConf.userCfg['keys']) 939 940 del d.get_new_keys_name 941 942 def test_on_bindingslist_select(self): 943 d = self.page 944 b = d.bindingslist 945 b.delete(0, 'end') 946 b.insert(0, 'copy') 947 b.insert(1, 'find') 948 b.activate(0) 949 950 b.focus_force() 951 b.see(1) 952 b.update() 953 x, y, dx, dy = b.bbox(1) 954 x += dx // 2 955 y += dy // 2 956 b.event_generate('<Enter>', x=0, y=0) 957 b.event_generate('<Motion>', x=x, y=y) 958 b.event_generate('<Button-1>', x=x, y=y) 959 b.event_generate('<ButtonRelease-1>', x=x, y=y) 960 self.assertEqual(b.get('anchor'), 'find') 961 self.assertEqual(d.button_new_keys.state(), ()) 962 963 def test_create_new_key_set_and_save_new_key_set(self): 964 eq = self.assertEqual 965 d = self.page 966 967 # Use default as previously active keyset. 968 d.keyset_source.set(True) 969 d.builtin_name.set('IDLE Classic Windows') 970 first_new = 'my new custom key set' 971 second_new = 'my second custom keyset' 972 973 # No changes, so keysets are an exact copy. 974 self.assertNotIn(first_new, idleConf.userCfg) 975 d.create_new_key_set(first_new) 976 eq(idleConf.GetSectionList('user', 'keys'), [first_new]) 977 eq(idleConf.GetKeySet('IDLE Classic Windows'), 978 idleConf.GetKeySet(first_new)) 979 eq(d.custom_name.get(), first_new) 980 self.assertFalse(d.keyset_source.get()) # Use custom set. 981 eq(d.set_keys_type.called, 1) 982 983 # Test that changed keybindings are in new keyset. 984 changes.add_option('keys', first_new, 'copy', '<Key-F11>') 985 self.assertNotIn(second_new, idleConf.userCfg) 986 d.create_new_key_set(second_new) 987 eq(idleConf.GetSectionList('user', 'keys'), [first_new, second_new]) 988 self.assertNotEqual(idleConf.GetKeySet(first_new), 989 idleConf.GetKeySet(second_new)) 990 # Check that difference in keysets was in option `copy` from `changes`. 991 idleConf.SetOption('keys', first_new, 'copy', '<Key-F11>') 992 eq(idleConf.GetKeySet(first_new), idleConf.GetKeySet(second_new)) 993 994 def test_load_keys_list(self): 995 eq = self.assertEqual 996 d = self.page 997 gks = idleConf.GetKeySet = Func() 998 del d.load_keys_list 999 b = d.bindingslist 1000 1001 b.delete(0, 'end') 1002 b.insert(0, '<<find>>') 1003 b.insert(1, '<<help>>') 1004 gks.result = {'<<copy>>': ['<Control-Key-c>', '<Control-Key-C>'], 1005 '<<force-open-completions>>': ['<Control-Key-space>'], 1006 '<<spam>>': ['<Key-F11>']} 1007 changes.add_option('keys', 'my keys', 'spam', '<Shift-Key-a>') 1008 expected = ('copy - <Control-Key-c> <Control-Key-C>', 1009 'force-open-completions - <Control-Key-space>', 1010 'spam - <Shift-Key-a>') 1011 1012 # No current selection. 1013 d.load_keys_list('my keys') 1014 eq(b.get(0, 'end'), expected) 1015 eq(b.get('anchor'), '') 1016 eq(b.curselection(), ()) 1017 1018 # Check selection. 1019 b.selection_set(1) 1020 b.selection_anchor(1) 1021 d.load_keys_list('my keys') 1022 eq(b.get(0, 'end'), expected) 1023 eq(b.get('anchor'), 'force-open-completions - <Control-Key-space>') 1024 eq(b.curselection(), (1, )) 1025 1026 # Change selection. 1027 b.selection_set(2) 1028 b.selection_anchor(2) 1029 d.load_keys_list('my keys') 1030 eq(b.get(0, 'end'), expected) 1031 eq(b.get('anchor'), 'spam - <Shift-Key-a>') 1032 eq(b.curselection(), (2, )) 1033 d.load_keys_list = Func() 1034 1035 del idleConf.GetKeySet 1036 1037 def test_delete_custom_keys(self): 1038 eq = self.assertEqual 1039 d = self.page 1040 d.button_delete_custom_keys.state(('!disabled',)) 1041 yesno = d.askyesno = Func() 1042 dialog.deactivate_current_config = Func() 1043 dialog.activate_config_changes = Func() 1044 1045 keyset_name = 'spam key set' 1046 idleConf.userCfg['keys'].SetOption(keyset_name, 'name', 'value') 1047 keyspage[keyset_name] = {'option': 'True'} 1048 1049 # Force custom keyset. 1050 d.keyset_source.set(False) 1051 d.custom_name.set(keyset_name) 1052 1053 # Cancel deletion. 1054 yesno.result = False 1055 d.button_delete_custom_keys.invoke() 1056 eq(yesno.called, 1) 1057 eq(keyspage[keyset_name], {'option': 'True'}) 1058 eq(idleConf.GetSectionList('user', 'keys'), ['spam key set']) 1059 eq(dialog.deactivate_current_config.called, 0) 1060 eq(dialog.activate_config_changes.called, 0) 1061 eq(d.set_keys_type.called, 0) 1062 1063 # Confirm deletion. 1064 yesno.result = True 1065 d.button_delete_custom_keys.invoke() 1066 eq(yesno.called, 2) 1067 self.assertNotIn(keyset_name, keyspage) 1068 eq(idleConf.GetSectionList('user', 'keys'), []) 1069 eq(d.custom_keyset_on.state(), ('disabled',)) 1070 eq(d.custom_name.get(), '- no custom keys -') 1071 eq(dialog.deactivate_current_config.called, 1) 1072 eq(dialog.activate_config_changes.called, 1) 1073 eq(d.set_keys_type.called, 1) 1074 1075 del dialog.activate_config_changes, dialog.deactivate_current_config 1076 del d.askyesno 1077 1078 1079 class GenPageTest(unittest.TestCase): 1080 """Test that general tab widgets enable users to make changes. 1081 1082 Test that widget actions set vars, that var changes add 1083 options to changes and that helplist works correctly. 1084 """ 1085 @classmethod 1086 def setUpClass(cls): 1087 page = cls.page = dialog.genpage 1088 dialog.note.select(page) 1089 page.set = page.set_add_delete_state = Func() 1090 page.upc = page.update_help_changes = Func() 1091 page.update() 1092 1093 @classmethod 1094 def tearDownClass(cls): 1095 page = cls.page 1096 del page.set, page.set_add_delete_state 1097 del page.upc, page.update_help_changes 1098 page.helplist.delete(0, 'end') 1099 page.user_helplist.clear() 1100 1101 def setUp(self): 1102 changes.clear() 1103 1104 def test_load_general_cfg(self): 1105 # Set to wrong values, load, check right values. 1106 eq = self.assertEqual 1107 d = self.page 1108 d.startup_edit.set(1) 1109 d.autosave.set(1) 1110 d.win_width.set(1) 1111 d.win_height.set(1) 1112 d.helplist.insert('end', 'bad') 1113 d.user_helplist = ['bad', 'worse'] 1114 idleConf.SetOption('main', 'HelpFiles', '1', 'name;file') 1115 d.load_general_cfg() 1116 eq(d.startup_edit.get(), 0) 1117 eq(d.autosave.get(), 0) 1118 eq(d.win_width.get(), '80') 1119 eq(d.win_height.get(), '40') 1120 eq(d.helplist.get(0, 'end'), ('name',)) 1121 eq(d.user_helplist, [('name', 'file', '1')]) 1122 1123 def test_startup(self): 1124 d = self.page 1125 d.startup_editor_on.invoke() 1126 self.assertEqual(mainpage, 1127 {'General': {'editor-on-startup': '1'}}) 1128 changes.clear() 1129 d.startup_shell_on.invoke() 1130 self.assertEqual(mainpage, 1131 {'General': {'editor-on-startup': '0'}}) 1132 1133 def test_editor_size(self): 1134 d = self.page 1135 d.win_height_int.delete(0, 'end') 1136 d.win_height_int.insert(0, '11') 1137 self.assertEqual(mainpage, {'EditorWindow': {'height': '11'}}) 1138 changes.clear() 1139 d.win_width_int.delete(0, 'end') 1140 d.win_width_int.insert(0, '11') 1141 self.assertEqual(mainpage, {'EditorWindow': {'width': '11'}}) 1142 1143 def test_autocomplete_wait(self): 1144 self.page.auto_wait_int.delete(0, 'end') 1145 self.page.auto_wait_int.insert(0, '11') 1146 self.assertEqual(extpage, {'AutoComplete': {'popupwait': '11'}}) 1147 1148 def test_parenmatch(self): 1149 d = self.page 1150 eq = self.assertEqual 1151 d.paren_style_type['menu'].invoke(0) 1152 eq(extpage, {'ParenMatch': {'style': 'opener'}}) 1153 changes.clear() 1154 d.paren_flash_time.delete(0, 'end') 1155 d.paren_flash_time.insert(0, '11') 1156 eq(extpage, {'ParenMatch': {'flash-delay': '11'}}) 1157 changes.clear() 1158 d.bell_on.invoke() 1159 eq(extpage, {'ParenMatch': {'bell': 'False'}}) 1160 1161 def test_autosave(self): 1162 d = self.page 1163 d.save_auto_on.invoke() 1164 self.assertEqual(mainpage, {'General': {'autosave': '1'}}) 1165 d.save_ask_on.invoke() 1166 self.assertEqual(mainpage, {'General': {'autosave': '0'}}) 1167 1168 def test_paragraph(self): 1169 self.page.format_width_int.delete(0, 'end') 1170 self.page.format_width_int.insert(0, '11') 1171 self.assertEqual(extpage, {'FormatParagraph': {'max-width': '11'}}) 1172 1173 def test_context(self): 1174 self.page.context_int.delete(0, 'end') 1175 self.page.context_int.insert(0, '1') 1176 self.assertEqual(extpage, {'CodeContext': {'maxlines': '1'}}) 1177 1178 def test_source_selected(self): 1179 d = self.page 1180 d.set = d.set_add_delete_state 1181 d.upc = d.update_help_changes 1182 helplist = d.helplist 1183 dex = 'end' 1184 helplist.insert(dex, 'source') 1185 helplist.activate(dex) 1186 1187 helplist.focus_force() 1188 helplist.see(dex) 1189 helplist.update() 1190 x, y, dx, dy = helplist.bbox(dex) 1191 x += dx // 2 1192 y += dy // 2 1193 d.set.called = d.upc.called = 0 1194 helplist.event_generate('<Enter>', x=0, y=0) 1195 helplist.event_generate('<Motion>', x=x, y=y) 1196 helplist.event_generate('<Button-1>', x=x, y=y) 1197 helplist.event_generate('<ButtonRelease-1>', x=x, y=y) 1198 self.assertEqual(helplist.get('anchor'), 'source') 1199 self.assertTrue(d.set.called) 1200 self.assertFalse(d.upc.called) 1201 1202 def test_set_add_delete_state(self): 1203 # Call with 0 items, 1 unselected item, 1 selected item. 1204 eq = self.assertEqual 1205 d = self.page 1206 del d.set_add_delete_state # Unmask method. 1207 sad = d.set_add_delete_state 1208 h = d.helplist 1209 1210 h.delete(0, 'end') 1211 sad() 1212 eq(d.button_helplist_edit.state(), ('disabled',)) 1213 eq(d.button_helplist_remove.state(), ('disabled',)) 1214 1215 h.insert(0, 'source') 1216 sad() 1217 eq(d.button_helplist_edit.state(), ('disabled',)) 1218 eq(d.button_helplist_remove.state(), ('disabled',)) 1219 1220 h.selection_set(0) 1221 sad() 1222 eq(d.button_helplist_edit.state(), ()) 1223 eq(d.button_helplist_remove.state(), ()) 1224 d.set_add_delete_state = Func() # Mask method. 1225 1226 def test_helplist_item_add(self): 1227 # Call without and twice with HelpSource result. 1228 # Double call enables check on order. 1229 eq = self.assertEqual 1230 orig_helpsource = configdialog.HelpSource 1231 hs = configdialog.HelpSource = Func(return_self=True) 1232 d = self.page 1233 d.helplist.delete(0, 'end') 1234 d.user_helplist.clear() 1235 d.set.called = d.upc.called = 0 1236 1237 hs.result = '' 1238 d.helplist_item_add() 1239 self.assertTrue(list(d.helplist.get(0, 'end')) == 1240 d.user_helplist == []) 1241 self.assertFalse(d.upc.called) 1242 1243 hs.result = ('name1', 'file1') 1244 d.helplist_item_add() 1245 hs.result = ('name2', 'file2') 1246 d.helplist_item_add() 1247 eq(d.helplist.get(0, 'end'), ('name1', 'name2')) 1248 eq(d.user_helplist, [('name1', 'file1'), ('name2', 'file2')]) 1249 eq(d.upc.called, 2) 1250 self.assertFalse(d.set.called) 1251 1252 configdialog.HelpSource = orig_helpsource 1253 1254 def test_helplist_item_edit(self): 1255 # Call without and with HelpSource change. 1256 eq = self.assertEqual 1257 orig_helpsource = configdialog.HelpSource 1258 hs = configdialog.HelpSource = Func(return_self=True) 1259 d = self.page 1260 d.helplist.delete(0, 'end') 1261 d.helplist.insert(0, 'name1') 1262 d.helplist.selection_set(0) 1263 d.helplist.selection_anchor(0) 1264 d.user_helplist.clear() 1265 d.user_helplist.append(('name1', 'file1')) 1266 d.set.called = d.upc.called = 0 1267 1268 hs.result = '' 1269 d.helplist_item_edit() 1270 hs.result = ('name1', 'file1') 1271 d.helplist_item_edit() 1272 eq(d.helplist.get(0, 'end'), ('name1',)) 1273 eq(d.user_helplist, [('name1', 'file1')]) 1274 self.assertFalse(d.upc.called) 1275 1276 hs.result = ('name2', 'file2') 1277 d.helplist_item_edit() 1278 eq(d.helplist.get(0, 'end'), ('name2',)) 1279 eq(d.user_helplist, [('name2', 'file2')]) 1280 self.assertTrue(d.upc.called == d.set.called == 1) 1281 1282 configdialog.HelpSource = orig_helpsource 1283 1284 def test_helplist_item_remove(self): 1285 eq = self.assertEqual 1286 d = self.page 1287 d.helplist.delete(0, 'end') 1288 d.helplist.insert(0, 'name1') 1289 d.helplist.selection_set(0) 1290 d.helplist.selection_anchor(0) 1291 d.user_helplist.clear() 1292 d.user_helplist.append(('name1', 'file1')) 1293 d.set.called = d.upc.called = 0 1294 1295 d.helplist_item_remove() 1296 eq(d.helplist.get(0, 'end'), ()) 1297 eq(d.user_helplist, []) 1298 self.assertTrue(d.upc.called == d.set.called == 1) 1299 1300 def test_update_help_changes(self): 1301 d = self.page 1302 del d.update_help_changes 1303 d.user_helplist.clear() 1304 d.user_helplist.append(('name1', 'file1')) 1305 d.user_helplist.append(('name2', 'file2')) 1306 1307 d.update_help_changes() 1308 self.assertEqual(mainpage['HelpFiles'], 1309 {'1': 'name1;file1', '2': 'name2;file2'}) 1310 d.update_help_changes = Func() 1311 1312 1313 class VarTraceTest(unittest.TestCase): 1314 1315 @classmethod 1316 def setUpClass(cls): 1317 cls.tracers = configdialog.VarTrace() 1318 cls.iv = IntVar(root) 1319 cls.bv = BooleanVar(root) 1320 1321 @classmethod 1322 def tearDownClass(cls): 1323 del cls.tracers, cls.iv, cls.bv 1324 1325 def setUp(self): 1326 self.tracers.clear() 1327 self.called = 0 1328 1329 def var_changed_increment(self, *params): 1330 self.called += 13 1331 1332 def var_changed_boolean(self, *params): 1333 pass 1334 1335 def test_init(self): 1336 tr = self.tracers 1337 tr.__init__() 1338 self.assertEqual(tr.untraced, []) 1339 self.assertEqual(tr.traced, []) 1340 1341 def test_clear(self): 1342 tr = self.tracers 1343 tr.untraced.append(0) 1344 tr.traced.append(1) 1345 tr.clear() 1346 self.assertEqual(tr.untraced, []) 1347 self.assertEqual(tr.traced, []) 1348 1349 def test_add(self): 1350 tr = self.tracers 1351 func = Func() 1352 cb = tr.make_callback = mock.Mock(return_value=func) 1353 1354 iv = tr.add(self.iv, self.var_changed_increment) 1355 self.assertIs(iv, self.iv) 1356 bv = tr.add(self.bv, self.var_changed_boolean) 1357 self.assertIs(bv, self.bv) 1358 1359 sv = StringVar(root) 1360 sv2 = tr.add(sv, ('main', 'section', 'option')) 1361 self.assertIs(sv2, sv) 1362 cb.assert_called_once() 1363 cb.assert_called_with(sv, ('main', 'section', 'option')) 1364 1365 expected = [(iv, self.var_changed_increment), 1366 (bv, self.var_changed_boolean), 1367 (sv, func)] 1368 self.assertEqual(tr.traced, []) 1369 self.assertEqual(tr.untraced, expected) 1370 1371 del tr.make_callback 1372 1373 def test_make_callback(self): 1374 cb = self.tracers.make_callback(self.iv, ('main', 'section', 'option')) 1375 self.assertTrue(callable(cb)) 1376 self.iv.set(42) 1377 # Not attached, so set didn't invoke the callback. 1378 self.assertNotIn('section', changes['main']) 1379 # Invoke callback manually. 1380 cb() 1381 self.assertIn('section', changes['main']) 1382 self.assertEqual(changes['main']['section']['option'], '42') 1383 changes.clear() 1384 1385 def test_attach_detach(self): 1386 tr = self.tracers 1387 iv = tr.add(self.iv, self.var_changed_increment) 1388 bv = tr.add(self.bv, self.var_changed_boolean) 1389 expected = [(iv, self.var_changed_increment), 1390 (bv, self.var_changed_boolean)] 1391 1392 # Attach callbacks and test call increment. 1393 tr.attach() 1394 self.assertEqual(tr.untraced, []) 1395 self.assertCountEqual(tr.traced, expected) 1396 iv.set(1) 1397 self.assertEqual(iv.get(), 1) 1398 self.assertEqual(self.called, 13) 1399 1400 # Check that only one callback is attached to a variable. 1401 # If more than one callback were attached, then var_changed_increment 1402 # would be called twice and the counter would be 2. 1403 self.called = 0 1404 tr.attach() 1405 iv.set(1) 1406 self.assertEqual(self.called, 13) 1407 1408 # Detach callbacks. 1409 self.called = 0 1410 tr.detach() 1411 self.assertEqual(tr.traced, []) 1412 self.assertCountEqual(tr.untraced, expected) 1413 iv.set(1) 1414 self.assertEqual(self.called, 0) 1415 1416 1417 if __name__ == '__main__': 1418 unittest.main(verbosity=2) 1419