Repository navigation
bpo-30780: Add IDLE configdialog tests #3592
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 5 commits
Commits
Show all changes
11 commits
Select commit
Hold shift + click to select a range
ef4584c
bpo-30780: IDLE: Complete keys and highlight coverage for configdialo…
csabella 648b850
Correct tests due to other changes
csabella bcea0fb
Merge branch 'master' into bpo30780
terryjreedy 45e60ff
Fix test failure - cannot use patch for instance addition and deletion.
terryjreedy 5a8e94f
Use mock.patch.object as context manager.
terryjreedy 14e9efc
Test selection block bindings.
terryjreedy 0b234eb
Eliminate reason to test with duplicate new names.
terryjreedy 168d07e
Complete last commit, which changed highlight code
terryjreedy 18499bf
News items.
terryjreedy 60982c4
Update NEWS.txt
terryjreedy c7d7ce2
Update 2020-01-27-16-44-29.bpo-30780.nR80qu.rst
terryjreedy File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -8,7 +8,8 @@ | |
| import unittest | ||
| from unittest import mock | ||
| from idlelib.idle_test.mock_idle import Func | ||
| from tkinter import Tk, StringVar, IntVar, BooleanVar, DISABLED, NORMAL | ||
| from tkinter import (Tk, StringVar, IntVar, BooleanVar, DISABLED, | ||
| NORMAL, TclError) | ||
| from idlelib import config | ||
| from idlelib.configdialog import idleConf, changes, tracers | ||
|
|
||
|
|
@@ -30,13 +31,15 @@ | |
| keyspage = changes['keys'] | ||
| extpage = changes['extensions'] | ||
|
|
||
|
|
||
| def setUpModule(): | ||
| global root, dialog | ||
| idleConf.userCfg = testcfg | ||
| root = Tk() | ||
| # root.withdraw() # Comment out, see issue 30870 | ||
| dialog = configdialog.ConfigDialog(root, 'Test', _utest=True) | ||
|
|
||
|
|
||
| def tearDownModule(): | ||
| global root, dialog | ||
| idleConf.userCfg = usercfg | ||
|
|
@@ -48,22 +51,56 @@ def tearDownModule(): | |
| root = dialog = None | ||
|
|
||
|
|
||
| class DialogTest(unittest.TestCase): | ||
| class ConfigDialogTest(unittest.TestCase): | ||
|
|
||
| def test_deactivate_current_config(self): | ||
| pass | ||
|
|
||
| def activate_config_changes(self): | ||
| pass | ||
|
|
||
|
|
||
| @mock.patch(__name__+'.dialog.destroy', new_callable=Func) | ||
| def test_cancel(self, destroy): | ||
| class ButtonTest(unittest.TestCase): | ||
|
|
||
| def test_click_ok(self): | ||
| d = dialog | ||
| apply = d.apply = mock.Mock() | ||
| destroy = d.destroy = mock.Mock() | ||
| d.buttons['Ok'].invoke() | ||
| apply.assert_called_once() | ||
| destroy.assert_called_once() | ||
| del d.destroy, d.apply | ||
|
|
||
| def test_click_apply(self): | ||
| d = dialog | ||
| deactivate = d.deactivate_current_config = mock.Mock() | ||
| save_ext = d.save_all_changed_extensions = mock.Mock() | ||
| activate = d.activate_config_changes = mock.Mock() | ||
| d.buttons['Apply'].invoke() | ||
| deactivate.assert_called_once() | ||
| save_ext.assert_called_once() | ||
| activate.assert_called_once() | ||
| del d.save_all_changed_extensions | ||
| del d.activate_config_changes, d.deactivate_current_config | ||
|
|
||
| def test_click_cancel(self): | ||
| d = dialog | ||
| d.destroy = Func() | ||
| changes['main']['something'] = 1 | ||
| dialog.cancel() | ||
| d.buttons['Cancel'].invoke() | ||
| self.assertEqual(changes['main'], {}) | ||
| self.assertEqual(destroy.called, 1) | ||
| self.assertEqual(d.destroy.called, 1) | ||
| del d.destroy | ||
|
|
||
| @mock.patch('idlelib.configdialog.view_text', new_callable=Func) | ||
| def test_help(self, view): | ||
| def test_click_help(self): | ||
| dialog.note.select(dialog.keyspage) | ||
| dialog.help() | ||
| s = view.kwds['contents'] | ||
| self.assertTrue(s.startswith('When you click') and | ||
| s.endswith('a different name.\n')) | ||
| with mock.patch.object(configdialog, 'view_text', | ||
| new_callable=Func) as view: | ||
| dialog.buttons['Help'].invoke() | ||
| title, contents = view.kwds['title'], view.kwds['contents'] | ||
| self.assertEqual(title, 'Help for IDLE preferences') | ||
| self.assertTrue(contents.startswith('When you click') and | ||
| contents.endswith('a different name.\n')) | ||
|
|
||
|
|
||
| class FontPageTest(unittest.TestCase): | ||
|
|
@@ -438,6 +475,90 @@ def click_it(start): | |
| eq(d.highlight_target.get(), elem[tag]) | ||
| eq(d.set_highlight_target.called, count) | ||
|
|
||
| def test_highlight_sample_double_click(self): | ||
| # Test double click on highlight_sample. | ||
| eq = self.assertEqual | ||
| d = self.page | ||
|
|
||
| hs = d.highlight_sample | ||
| hs.focus_force() | ||
| hs.see(1.0) | ||
| hs.update_idletasks() | ||
|
|
||
| # Test binding from configdialog. | ||
| hs.event_generate('<Enter>', x=0, y=0) | ||
|
terryjreedy marked this conversation as resolved.
Resolved
|
||
| hs.event_generate('<Motion>', x=0, y=0) | ||
| # Double click is a sequence of two clicks in a row. | ||
| for _ in range(2): | ||
| hs.event_generate('<ButtonPress-1>', x=0, y=0) | ||
| hs.event_generate('<ButtonRelease-1>', x=0, y=0) | ||
|
|
||
| self.assertNotEqual(d.highlight_target.get(), 'text double') | ||
|
terryjreedy marked this conversation as resolved.
Resolved
|
||
| with self.assertRaises(TclError, msg='text doesn\'t contain any characters tagged with "sel"'): | ||
| hs.get('sel.first', 'sel.last') | ||
|
|
||
| # Change binding on double click. This will allow the event to | ||
|
terryjreedy marked this conversation as resolved.
Resolved
|
||
| # propagate to the other levels. The default binding for double | ||
| # click is to select the word that was double clicked. | ||
| hs.bind('<Double-Button-1>', lambda e: d.highlight_target.set('test double')) | ||
|
|
||
| hs.event_generate('<Enter>', x=0, y=0) | ||
| hs.event_generate('<Motion>', x=0, y=0) | ||
| # Double click is a sequence of two clicks in a row. | ||
| for _ in range(2): | ||
| hs.event_generate('<ButtonPress-1>', x=0, y=0) | ||
| hs.event_generate('<ButtonRelease-1>', x=0, y=0) | ||
|
|
||
| eq(d.highlight_target.get(), 'test double') | ||
| eq(hs.get('sel.first', 'sel.last'), ' 1 # Click selects item.\n') | ||
|
|
||
| # Remove selection and rebind. | ||
| hs.tag_remove('sel', '0.0', 'end') | ||
| hs.bind('<Double-Button-1>', lambda e: 'break') | ||
|
|
||
| def test_highlight_sample_b1_motion(self): | ||
| # Test button motion on highlight_sample. | ||
| eq = self.assertEqual | ||
| d = self.page | ||
|
|
||
| hs = d.highlight_sample | ||
| hs.focus_force() | ||
| hs.see(1.0) | ||
| hs.update_idletasks() | ||
|
|
||
| x, y, dx, dy, offset = hs.dlineinfo('1.0') | ||
|
|
||
| # Test binding from configdialog. | ||
| hs.event_generate('<Leave>') | ||
| hs.event_generate('<Enter>') | ||
| hs.event_generate('<Motion>', x=x, y=y) | ||
| hs.event_generate('<ButtonPress-1>', x=x, y=y) | ||
| hs.event_generate('<B1-Motion>', x=dx, y=dy) | ||
| hs.event_generate('<ButtonRelease-1>', x=dx, y=dy) | ||
|
|
||
| eq(hs.tag_ranges('sel'), ()) | ||
|
|
||
| # Remove binding on button motion. This will allow the event to | ||
| # propagate to the other levels. The default binding for button | ||
| # motion is to select the text that was moused over while the button | ||
| # is held. | ||
| hs.unbind('<B1-Motion>') | ||
| x, y, dx, dy, offset = hs.dlineinfo('4.0') | ||
|
|
||
| hs.event_generate('<Leave>') | ||
| hs.event_generate('<Enter>') | ||
| hs.event_generate('<Motion>', x=x, y=y) | ||
| hs.event_generate('<ButtonPress-1>', x=x, y=y) | ||
| hs.event_generate('<B1-Motion>', x=x+dx, y=y+dy) | ||
| hs.event_generate('<ButtonRelease-1>', x=x+dx, y=y+dy) | ||
|
|
||
| self.assertNotEqual(hs.tag_ranges('sel'), ()) | ||
| self.assertIn('def', hs.get('sel.first', 'sel.last')) | ||
|
|
||
| # Remove selection and rebind. | ||
| hs.tag_remove('sel', '0.0', 'end') | ||
| hs.bind('<B1-Motion>', lambda e: 'break') | ||
|
|
||
| def test_set_theme_type(self): | ||
| eq = self.assertEqual | ||
| d = self.page | ||
|
|
@@ -568,6 +689,17 @@ def test_create_new_and_save_new(self): | |
| self.assertFalse(d.theme_source.get()) # Use custom set. | ||
| eq(d.set_theme_type.called, 1) | ||
|
|
||
| # Call with same name as first time to make sure it adds it only once. | ||
|
terryjreedy marked this conversation as resolved.
Resolved
|
||
| d.theme_source.set(True) | ||
| d.builtin_name.set('IDLE Classic') | ||
| d.create_new(first_new) | ||
| eq(idleConf.GetSectionList('user', 'highlight'), [first_new]) | ||
| eq(idleConf.GetThemeDict('default', 'IDLE Classic'), | ||
| idleConf.GetThemeDict('user', first_new)) | ||
| eq(d.custom_name.get(), first_new) | ||
| self.assertFalse(d.theme_source.get()) # Use custom set. | ||
| eq(d.set_theme_type.called, 2) | ||
|
|
||
| # Test that changed targets are in new theme. | ||
| changes.add_option('highlight', first_new, 'hit-background', 'yellow') | ||
| self.assertNotIn(second_new, idleConf.userCfg) | ||
|
|
@@ -666,16 +798,21 @@ def test_delete_custom(self): | |
| idleConf.userCfg['highlight'].SetOption(theme_name, 'name', 'value') | ||
| highpage[theme_name] = {'option': 'True'} | ||
|
|
||
| theme_name2 = 'other theme' | ||
| idleConf.userCfg['highlight'].SetOption(theme_name2, 'name', 'value') | ||
| highpage[theme_name2] = {'option': 'False'} | ||
|
|
||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This and the corresponding additions for keys cover the failure of |
||
| # Force custom theme. | ||
| d.theme_source.set(False) | ||
| d.custom_theme_on.state(('!disabled',)) | ||
| d.custom_theme_on.invoke() | ||
| d.custom_name.set(theme_name) | ||
|
|
||
| # Cancel deletion. | ||
| yesno.result = False | ||
| d.button_delete_custom.invoke() | ||
| eq(yesno.called, 1) | ||
| eq(highpage[theme_name], {'option': 'True'}) | ||
| eq(idleConf.GetSectionList('user', 'highlight'), ['spam theme']) | ||
| eq(idleConf.GetSectionList('user', 'highlight'), [theme_name, theme_name2]) | ||
| eq(dialog.deactivate_current_config.called, 0) | ||
| eq(dialog.activate_config_changes.called, 0) | ||
| eq(d.set_theme_type.called, 0) | ||
|
|
@@ -685,13 +822,26 @@ def test_delete_custom(self): | |
| d.button_delete_custom.invoke() | ||
| eq(yesno.called, 2) | ||
| self.assertNotIn(theme_name, highpage) | ||
| eq(idleConf.GetSectionList('user', 'highlight'), []) | ||
| eq(d.custom_theme_on.state(), ('disabled',)) | ||
| eq(d.custom_name.get(), '- no custom themes -') | ||
| eq(idleConf.GetSectionList('user', 'highlight'), [theme_name2]) | ||
| eq(d.custom_theme_on.state(), ()) | ||
| eq(d.custom_name.get(), theme_name2) | ||
| eq(dialog.deactivate_current_config.called, 1) | ||
| eq(dialog.activate_config_changes.called, 1) | ||
| eq(d.set_theme_type.called, 1) | ||
|
|
||
| # Confirm deletion of second theme - empties list. | ||
| d.custom_name.set(theme_name2) | ||
| yesno.result = True | ||
| d.button_delete_custom.invoke() | ||
| eq(yesno.called, 3) | ||
| self.assertNotIn(theme_name, highpage) | ||
| eq(idleConf.GetSectionList('user', 'highlight'), []) | ||
| eq(d.custom_theme_on.state(), ('disabled',)) | ||
| eq(d.custom_name.get(), '- no custom themes -') | ||
| eq(dialog.deactivate_current_config.called, 2) | ||
| eq(dialog.activate_config_changes.called, 2) | ||
| eq(d.set_theme_type.called, 2) | ||
|
|
||
| del dialog.activate_config_changes, dialog.deactivate_current_config | ||
| del d.askyesno | ||
|
|
||
|
|
@@ -993,6 +1143,17 @@ def test_create_new_key_set_and_save_new_key_set(self): | |
| self.assertFalse(d.keyset_source.get()) # Use custom set. | ||
| eq(d.set_keys_type.called, 1) | ||
|
|
||
| # Call with same name as first time to make sure it adds it only once. | ||
| d.keyset_source.set(True) | ||
| d.builtin_name.set('IDLE Classic Windows') | ||
| d.create_new_key_set(first_new) | ||
| eq(idleConf.GetSectionList('user', 'keys'), [first_new]) | ||
| eq(idleConf.GetKeySet('IDLE Classic Windows'), | ||
| idleConf.GetKeySet(first_new)) | ||
| eq(d.custom_name.get(), first_new) | ||
| self.assertFalse(d.keyset_source.get()) # Use custom set. | ||
| eq(d.set_keys_type.called, 2) | ||
|
|
||
| # Test that changed keybindings are in new keyset. | ||
| changes.add_option('keys', first_new, 'copy', '<Key-F11>') | ||
| self.assertNotIn(second_new, idleConf.userCfg) | ||
|
|
@@ -1059,16 +1220,21 @@ def test_delete_custom_keys(self): | |
| idleConf.userCfg['keys'].SetOption(keyset_name, 'name', 'value') | ||
| keyspage[keyset_name] = {'option': 'True'} | ||
|
|
||
| keyset_name2 = 'other key set' | ||
| idleConf.userCfg['keys'].SetOption(keyset_name2, 'name', 'value') | ||
| keyspage[keyset_name2] = {'option': 'False'} | ||
|
|
||
| # Force custom keyset. | ||
| d.keyset_source.set(False) | ||
| d.custom_keyset_on.state(('!disabled',)) | ||
| d.custom_keyset_on.invoke() | ||
| d.custom_name.set(keyset_name) | ||
|
|
||
| # Cancel deletion. | ||
| yesno.result = False | ||
| d.button_delete_custom_keys.invoke() | ||
| eq(yesno.called, 1) | ||
| eq(keyspage[keyset_name], {'option': 'True'}) | ||
| eq(idleConf.GetSectionList('user', 'keys'), ['spam key set']) | ||
| eq(idleConf.GetSectionList('user', 'keys'), [keyset_name, keyset_name2]) | ||
| eq(dialog.deactivate_current_config.called, 0) | ||
| eq(dialog.activate_config_changes.called, 0) | ||
| eq(d.set_keys_type.called, 0) | ||
|
|
@@ -1078,13 +1244,26 @@ def test_delete_custom_keys(self): | |
| d.button_delete_custom_keys.invoke() | ||
| eq(yesno.called, 2) | ||
| self.assertNotIn(keyset_name, keyspage) | ||
| eq(idleConf.GetSectionList('user', 'keys'), []) | ||
| eq(d.custom_keyset_on.state(), ('disabled',)) | ||
| eq(d.custom_name.get(), '- no custom keys -') | ||
| eq(idleConf.GetSectionList('user', 'keys'), [keyset_name2]) | ||
| eq(d.custom_keyset_on.state(), ()) | ||
| eq(d.custom_name.get(), keyset_name2) | ||
| eq(dialog.deactivate_current_config.called, 1) | ||
| eq(dialog.activate_config_changes.called, 1) | ||
| eq(d.set_keys_type.called, 1) | ||
|
|
||
| # Confirm deletion of second keyset - empties list. | ||
| d.custom_name.set(keyset_name2) | ||
| yesno.result = True | ||
| d.button_delete_custom_keys.invoke() | ||
| eq(yesno.called, 3) | ||
| self.assertNotIn(keyset_name, keyspage) | ||
| eq(idleConf.GetSectionList('user', 'keys'), []) | ||
| eq(d.custom_keyset_on.state(), ('disabled',)) | ||
| eq(d.custom_name.get(), '- no custom keys -') | ||
| eq(dialog.deactivate_current_config.called, 2) | ||
| eq(dialog.activate_config_changes.called, 2) | ||
| eq(d.set_keys_type.called, 2) | ||
|
|
||
| del dialog.activate_config_changes, dialog.deactivate_current_config | ||
| del d.askyesno | ||
|
|
||
|
|
||
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.