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