1"""Test config_key, coverage 98%.
2
3Coverage is effectively 100%.  Tkinter dialog is mocked, Mac-only line
4may be skipped, and dummy function in bind test should not be called.
5Not tested: exit with 'self.advanced or self.keys_ok(keys)) ...' False.
6"""
7
8from idlelib import config_key
9from test.support import requires
10import unittest
11from unittest import mock
12from tkinter import Tk, TclError
13from idlelib.idle_test.mock_idle import Func
14from idlelib.idle_test.mock_tk import Mbox_func
15
16gkd = config_key.GetKeysDialog
17
18
19class ValidationTest(unittest.TestCase):
20    "Test validation methods: ok, keys_ok, bind_ok."
21
22    class Validator(gkd):
23        def __init__(self, *args, **kwargs):
24            config_key.GetKeysDialog.__init__(self, *args, **kwargs)
25            class list_keys_final:
26                get = Func()
27            self.list_keys_final = list_keys_final
28        get_modifiers = Func()
29        showerror = Mbox_func()
30
31    @classmethod
32    def setUpClass(cls):
33        requires('gui')
34        cls.root = Tk()
35        cls.root.withdraw()
36        keylist = [['<Key-F12>'], ['<Control-Key-x>', '<Control-Key-X>']]
37        cls.dialog = cls.Validator(
38            cls.root, 'Title', '<<Test>>', keylist, _utest=True)
39
40    @classmethod
41    def tearDownClass(cls):
42        cls.dialog.cancel()
43        cls.root.update_idletasks()
44        cls.root.destroy()
45        del cls.dialog, cls.root
46
47    def setUp(self):
48        self.dialog.showerror.message = ''
49    # A test that needs a particular final key value should set it.
50    # A test that sets a non-blank modifier list should reset it to [].
51
52    def test_ok_empty(self):
53        self.dialog.key_string.set(' ')
54        self.dialog.ok()
55        self.assertEqual(self.dialog.result, '')
56        self.assertEqual(self.dialog.showerror.message, 'No key specified.')
57
58    def test_ok_good(self):
59        self.dialog.key_string.set('<Key-F11>')
60        self.dialog.list_keys_final.get.result = 'F11'
61        self.dialog.ok()
62        self.assertEqual(self.dialog.result, '<Key-F11>')
63        self.assertEqual(self.dialog.showerror.message, '')
64
65    def test_keys_no_ending(self):
66        self.assertFalse(self.dialog.keys_ok('<Control-Shift'))
67        self.assertIn('Missing the final', self.dialog.showerror.message)
68
69    def test_keys_no_modifier_bad(self):
70        self.dialog.list_keys_final.get.result = 'A'
71        self.assertFalse(self.dialog.keys_ok('<Key-A>'))
72        self.assertIn('No modifier', self.dialog.showerror.message)
73
74    def test_keys_no_modifier_ok(self):
75        self.dialog.list_keys_final.get.result = 'F11'
76        self.assertTrue(self.dialog.keys_ok('<Key-F11>'))
77        self.assertEqual(self.dialog.showerror.message, '')
78
79    def test_keys_shift_bad(self):
80        self.dialog.list_keys_final.get.result = 'a'
81        self.dialog.get_modifiers.result = ['Shift']
82        self.assertFalse(self.dialog.keys_ok('<a>'))
83        self.assertIn('shift modifier', self.dialog.showerror.message)
84        self.dialog.get_modifiers.result = []
85
86    def test_keys_dup(self):
87        for mods, final, seq in (([], 'F12', '<Key-F12>'),
88                                 (['Control'], 'x', '<Control-Key-x>'),
89                                 (['Control'], 'X', '<Control-Key-X>')):
90            with self.subTest(m=mods, f=final, s=seq):
91                self.dialog.list_keys_final.get.result = final
92                self.dialog.get_modifiers.result = mods
93                self.assertFalse(self.dialog.keys_ok(seq))
94                self.assertIn('already in use', self.dialog.showerror.message)
95        self.dialog.get_modifiers.result = []
96
97    def test_bind_ok(self):
98        self.assertTrue(self.dialog.bind_ok('<Control-Shift-Key-a>'))
99        self.assertEqual(self.dialog.showerror.message, '')
100
101    def test_bind_not_ok(self):
102        self.assertFalse(self.dialog.bind_ok('<Control-Shift>'))
103        self.assertIn('not accepted', self.dialog.showerror.message)
104
105
106class ToggleLevelTest(unittest.TestCase):
107    "Test toggle between Basic and Advanced frames."
108
109    @classmethod
110    def setUpClass(cls):
111        requires('gui')
112        cls.root = Tk()
113        cls.root.withdraw()
114        cls.dialog = gkd(cls.root, 'Title', '<<Test>>', [], _utest=True)
115
116    @classmethod
117    def tearDownClass(cls):
118        cls.dialog.cancel()
119        cls.root.update_idletasks()
120        cls.root.destroy()
121        del cls.dialog, cls.root
122
123    def test_toggle_level(self):
124        dialog = self.dialog
125
126        def stackorder():
127            """Get the stack order of the children of the frame.
128
129            winfo_children() stores the children in stack order, so
130            this can be used to check whether a frame is above or
131            below another one.
132            """
133            for index, child in enumerate(dialog.frame.winfo_children()):
134                if child._name == 'keyseq_basic':
135                    basic = index
136                if child._name == 'keyseq_advanced':
137                    advanced = index
138            return basic, advanced
139
140        # New window starts at basic level.
141        self.assertFalse(dialog.advanced)
142        self.assertIn('Advanced', dialog.button_level['text'])
143        basic, advanced = stackorder()
144        self.assertGreater(basic, advanced)
145
146        # Toggle to advanced.
147        dialog.toggle_level()
148        self.assertTrue(dialog.advanced)
149        self.assertIn('Basic', dialog.button_level['text'])
150        basic, advanced = stackorder()
151        self.assertGreater(advanced, basic)
152
153        # Toggle to basic.
154        dialog.button_level.invoke()
155        self.assertFalse(dialog.advanced)
156        self.assertIn('Advanced', dialog.button_level['text'])
157        basic, advanced = stackorder()
158        self.assertGreater(basic, advanced)
159
160
161class KeySelectionTest(unittest.TestCase):
162    "Test selecting key on Basic frames."
163
164    class Basic(gkd):
165        def __init__(self, *args, **kwargs):
166            super().__init__(*args, **kwargs)
167            class list_keys_final:
168                get = Func()
169                select_clear = Func()
170                yview = Func()
171            self.list_keys_final = list_keys_final
172        def set_modifiers_for_platform(self):
173            self.modifiers = ['foo', 'bar', 'BAZ']
174            self.modifier_label = {'BAZ': 'ZZZ'}
175        showerror = Mbox_func()
176
177    @classmethod
178    def setUpClass(cls):
179        requires('gui')
180        cls.root = Tk()
181        cls.root.withdraw()
182        cls.dialog = cls.Basic(cls.root, 'Title', '<<Test>>', [], _utest=True)
183
184    @classmethod
185    def tearDownClass(cls):
186        cls.dialog.cancel()
187        cls.root.update_idletasks()
188        cls.root.destroy()
189        del cls.dialog, cls.root
190
191    def setUp(self):
192        self.dialog.clear_key_seq()
193
194    def test_get_modifiers(self):
195        dialog = self.dialog
196        gm = dialog.get_modifiers
197        eq = self.assertEqual
198
199        # Modifiers are set on/off by invoking the checkbutton.
200        dialog.modifier_checkbuttons['foo'].invoke()
201        eq(gm(), ['foo'])
202
203        dialog.modifier_checkbuttons['BAZ'].invoke()
204        eq(gm(), ['foo', 'BAZ'])
205
206        dialog.modifier_checkbuttons['foo'].invoke()
207        eq(gm(), ['BAZ'])
208
209    @mock.patch.object(gkd, 'get_modifiers')
210    def test_build_key_string(self, mock_modifiers):
211        dialog = self.dialog
212        key = dialog.list_keys_final
213        string = dialog.key_string.get
214        eq = self.assertEqual
215
216        key.get.result = 'a'
217        mock_modifiers.return_value = []
218        dialog.build_key_string()
219        eq(string(), '<Key-a>')
220
221        mock_modifiers.return_value = ['mymod']
222        dialog.build_key_string()
223        eq(string(), '<mymod-Key-a>')
224
225        key.get.result = ''
226        mock_modifiers.return_value = ['mymod', 'test']
227        dialog.build_key_string()
228        eq(string(), '<mymod-test>')
229
230    @mock.patch.object(gkd, 'get_modifiers')
231    def test_final_key_selected(self, mock_modifiers):
232        dialog = self.dialog
233        key = dialog.list_keys_final
234        string = dialog.key_string.get
235        eq = self.assertEqual
236
237        mock_modifiers.return_value = ['Shift']
238        key.get.result = '{'
239        dialog.final_key_selected()
240        eq(string(), '<Shift-Key-braceleft>')
241
242
243class CancelTest(unittest.TestCase):
244    "Simulate user clicking [Cancel] button."
245
246    @classmethod
247    def setUpClass(cls):
248        requires('gui')
249        cls.root = Tk()
250        cls.root.withdraw()
251        cls.dialog = gkd(cls.root, 'Title', '<<Test>>', [], _utest=True)
252
253    @classmethod
254    def tearDownClass(cls):
255        cls.dialog.cancel()
256        cls.root.update_idletasks()
257        cls.root.destroy()
258        del cls.dialog, cls.root
259
260    def test_cancel(self):
261        self.assertEqual(self.dialog.winfo_class(), 'Toplevel')
262        self.dialog.button_cancel.invoke()
263        with self.assertRaises(TclError):
264            self.dialog.winfo_class()
265        self.assertEqual(self.dialog.result, '')
266
267
268class HelperTest(unittest.TestCase):
269    "Test module level helper functions."
270
271    def test_translate_key(self):
272        tr = config_key.translate_key
273        eq = self.assertEqual
274
275        # Letters return unchanged with no 'Shift'.
276        eq(tr('q', []), 'Key-q')
277        eq(tr('q', ['Control', 'Alt']), 'Key-q')
278
279        # 'Shift' uppercases single lowercase letters.
280        eq(tr('q', ['Shift']), 'Key-Q')
281        eq(tr('q', ['Control', 'Shift']), 'Key-Q')
282        eq(tr('q', ['Control', 'Alt', 'Shift']), 'Key-Q')
283
284        # Convert key name to keysym.
285        eq(tr('Page Up', []), 'Key-Prior')
286        # 'Shift' doesn't change case when it's not a single char.
287        eq(tr('*', ['Shift']), 'Key-asterisk')
288
289
290if __name__ == '__main__':
291    unittest.main(verbosity=2)
292