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
16
17class ValidationTest(unittest.TestCase):
18    "Test validation methods: ok, keys_ok, bind_ok."
19
20    class Validator(config_key.GetKeysFrame):
21        def __init__(self, *args, **kwargs):
22            super().__init__(*args, **kwargs)
23            class list_keys_final:
24                get = Func()
25            self.list_keys_final = list_keys_final
26        get_modifiers = Func()
27        showerror = Mbox_func()
28
29    @classmethod
30    def setUpClass(cls):
31        requires('gui')
32        cls.root = Tk()
33        cls.root.withdraw()
34        keylist = [['<Key-F12>'], ['<Control-Key-x>', '<Control-Key-X>']]
35        cls.dialog = cls.Validator(cls.root, '<<Test>>', keylist)
36
37    @classmethod
38    def tearDownClass(cls):
39        del cls.dialog
40        cls.root.update_idletasks()
41        cls.root.destroy()
42        del cls.root
43
44    def setUp(self):
45        self.dialog.showerror.message = ''
46    # A test that needs a particular final key value should set it.
47    # A test that sets a non-blank modifier list should reset it to [].
48
49    def test_ok_empty(self):
50        self.dialog.key_string.set(' ')
51        self.dialog.ok()
52        self.assertEqual(self.dialog.result, '')
53        self.assertEqual(self.dialog.showerror.message, 'No key specified.')
54
55    def test_ok_good(self):
56        self.dialog.key_string.set('<Key-F11>')
57        self.dialog.list_keys_final.get.result = 'F11'
58        self.dialog.ok()
59        self.assertEqual(self.dialog.result, '<Key-F11>')
60        self.assertEqual(self.dialog.showerror.message, '')
61
62    def test_keys_no_ending(self):
63        self.assertFalse(self.dialog.keys_ok('<Control-Shift'))
64        self.assertIn('Missing the final', self.dialog.showerror.message)
65
66    def test_keys_no_modifier_bad(self):
67        self.dialog.list_keys_final.get.result = 'A'
68        self.assertFalse(self.dialog.keys_ok('<Key-A>'))
69        self.assertIn('No modifier', self.dialog.showerror.message)
70
71    def test_keys_no_modifier_ok(self):
72        self.dialog.list_keys_final.get.result = 'F11'
73        self.assertTrue(self.dialog.keys_ok('<Key-F11>'))
74        self.assertEqual(self.dialog.showerror.message, '')
75
76    def test_keys_shift_bad(self):
77        self.dialog.list_keys_final.get.result = 'a'
78        self.dialog.get_modifiers.result = ['Shift']
79        self.assertFalse(self.dialog.keys_ok('<a>'))
80        self.assertIn('shift modifier', self.dialog.showerror.message)
81        self.dialog.get_modifiers.result = []
82
83    def test_keys_dup(self):
84        for mods, final, seq in (([], 'F12', '<Key-F12>'),
85                                 (['Control'], 'x', '<Control-Key-x>'),
86                                 (['Control'], 'X', '<Control-Key-X>')):
87            with self.subTest(m=mods, f=final, s=seq):
88                self.dialog.list_keys_final.get.result = final
89                self.dialog.get_modifiers.result = mods
90                self.assertFalse(self.dialog.keys_ok(seq))
91                self.assertIn('already in use', self.dialog.showerror.message)
92        self.dialog.get_modifiers.result = []
93
94    def test_bind_ok(self):
95        self.assertTrue(self.dialog.bind_ok('<Control-Shift-Key-a>'))
96        self.assertEqual(self.dialog.showerror.message, '')
97
98    def test_bind_not_ok(self):
99        self.assertFalse(self.dialog.bind_ok('<Control-Shift>'))
100        self.assertIn('not accepted', self.dialog.showerror.message)
101
102
103class ToggleLevelTest(unittest.TestCase):
104    "Test toggle between Basic and Advanced frames."
105
106    @classmethod
107    def setUpClass(cls):
108        requires('gui')
109        cls.root = Tk()
110        cls.root.withdraw()
111        cls.dialog = config_key.GetKeysFrame(cls.root, '<<Test>>', [])
112
113    @classmethod
114    def tearDownClass(cls):
115        del cls.dialog
116        cls.root.update_idletasks()
117        cls.root.destroy()
118        del cls.root
119
120    def test_toggle_level(self):
121        dialog = self.dialog
122
123        def stackorder():
124            """Get the stack order of the children of the frame.
125
126            winfo_children() stores the children in stack order, so
127            this can be used to check whether a frame is above or
128            below another one.
129            """
130            for index, child in enumerate(dialog.winfo_children()):
131                if child._name == 'keyseq_basic':
132                    basic = index
133                if child._name == 'keyseq_advanced':
134                    advanced = index
135            return basic, advanced
136
137        # New window starts at basic level.
138        self.assertFalse(dialog.advanced)
139        self.assertIn('Advanced', dialog.button_level['text'])
140        basic, advanced = stackorder()
141        self.assertGreater(basic, advanced)
142
143        # Toggle to advanced.
144        dialog.toggle_level()
145        self.assertTrue(dialog.advanced)
146        self.assertIn('Basic', dialog.button_level['text'])
147        basic, advanced = stackorder()
148        self.assertGreater(advanced, basic)
149
150        # Toggle to basic.
151        dialog.button_level.invoke()
152        self.assertFalse(dialog.advanced)
153        self.assertIn('Advanced', dialog.button_level['text'])
154        basic, advanced = stackorder()
155        self.assertGreater(basic, advanced)
156
157
158class KeySelectionTest(unittest.TestCase):
159    "Test selecting key on Basic frames."
160
161    class Basic(config_key.GetKeysFrame):
162        def __init__(self, *args, **kwargs):
163            super().__init__(*args, **kwargs)
164            class list_keys_final:
165                get = Func()
166                select_clear = Func()
167                yview = Func()
168            self.list_keys_final = list_keys_final
169        def set_modifiers_for_platform(self):
170            self.modifiers = ['foo', 'bar', 'BAZ']
171            self.modifier_label = {'BAZ': 'ZZZ'}
172        showerror = Mbox_func()
173
174    @classmethod
175    def setUpClass(cls):
176        requires('gui')
177        cls.root = Tk()
178        cls.root.withdraw()
179        cls.dialog = cls.Basic(cls.root, '<<Test>>', [])
180
181    @classmethod
182    def tearDownClass(cls):
183        del cls.dialog
184        cls.root.update_idletasks()
185        cls.root.destroy()
186        del cls.root
187
188    def setUp(self):
189        self.dialog.clear_key_seq()
190
191    def test_get_modifiers(self):
192        dialog = self.dialog
193        gm = dialog.get_modifiers
194        eq = self.assertEqual
195
196        # Modifiers are set on/off by invoking the checkbutton.
197        dialog.modifier_checkbuttons['foo'].invoke()
198        eq(gm(), ['foo'])
199
200        dialog.modifier_checkbuttons['BAZ'].invoke()
201        eq(gm(), ['foo', 'BAZ'])
202
203        dialog.modifier_checkbuttons['foo'].invoke()
204        eq(gm(), ['BAZ'])
205
206    @mock.patch.object(config_key.GetKeysFrame, 'get_modifiers')
207    def test_build_key_string(self, mock_modifiers):
208        dialog = self.dialog
209        key = dialog.list_keys_final
210        string = dialog.key_string.get
211        eq = self.assertEqual
212
213        key.get.result = 'a'
214        mock_modifiers.return_value = []
215        dialog.build_key_string()
216        eq(string(), '<Key-a>')
217
218        mock_modifiers.return_value = ['mymod']
219        dialog.build_key_string()
220        eq(string(), '<mymod-Key-a>')
221
222        key.get.result = ''
223        mock_modifiers.return_value = ['mymod', 'test']
224        dialog.build_key_string()
225        eq(string(), '<mymod-test>')
226
227    @mock.patch.object(config_key.GetKeysFrame, 'get_modifiers')
228    def test_final_key_selected(self, mock_modifiers):
229        dialog = self.dialog
230        key = dialog.list_keys_final
231        string = dialog.key_string.get
232        eq = self.assertEqual
233
234        mock_modifiers.return_value = ['Shift']
235        key.get.result = '{'
236        dialog.final_key_selected()
237        eq(string(), '<Shift-Key-braceleft>')
238
239
240class CancelWindowTest(unittest.TestCase):
241    "Simulate user clicking [Cancel] button."
242
243    @classmethod
244    def setUpClass(cls):
245        requires('gui')
246        cls.root = Tk()
247        cls.root.withdraw()
248        cls.dialog = config_key.GetKeysWindow(
249            cls.root, 'Title', '<<Test>>', [], _utest=True)
250
251    @classmethod
252    def tearDownClass(cls):
253        cls.dialog.cancel()
254        del cls.dialog
255        cls.root.update_idletasks()
256        cls.root.destroy()
257        del cls.root
258
259    @mock.patch.object(config_key.GetKeysFrame, 'ok')
260    def test_cancel(self, mock_frame_ok):
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        mock_frame_ok.assert_not_called()
267
268
269class OKWindowTest(unittest.TestCase):
270    "Simulate user clicking [OK] button."
271
272    @classmethod
273    def setUpClass(cls):
274        requires('gui')
275        cls.root = Tk()
276        cls.root.withdraw()
277        cls.dialog = config_key.GetKeysWindow(
278            cls.root, 'Title', '<<Test>>', [], _utest=True)
279
280    @classmethod
281    def tearDownClass(cls):
282        cls.dialog.cancel()
283        del cls.dialog
284        cls.root.update_idletasks()
285        cls.root.destroy()
286        del cls.root
287
288    @mock.patch.object(config_key.GetKeysFrame, 'ok')
289    def test_ok(self, mock_frame_ok):
290        self.assertEqual(self.dialog.winfo_class(), 'Toplevel')
291        self.dialog.button_ok.invoke()
292        with self.assertRaises(TclError):
293            self.dialog.winfo_class()
294        mock_frame_ok.assert_called()
295
296
297class WindowResultTest(unittest.TestCase):
298    "Test window result get and set."
299
300    @classmethod
301    def setUpClass(cls):
302        requires('gui')
303        cls.root = Tk()
304        cls.root.withdraw()
305        cls.dialog = config_key.GetKeysWindow(
306            cls.root, 'Title', '<<Test>>', [], _utest=True)
307
308    @classmethod
309    def tearDownClass(cls):
310        cls.dialog.cancel()
311        del cls.dialog
312        cls.root.update_idletasks()
313        cls.root.destroy()
314        del cls.root
315
316    def test_result(self):
317        dialog = self.dialog
318        eq = self.assertEqual
319
320        dialog.result = ''
321        eq(dialog.result, '')
322        eq(dialog.frame.result,'')
323
324        dialog.result = 'bar'
325        eq(dialog.result,'bar')
326        eq(dialog.frame.result,'bar')
327
328        dialog.frame.result = 'foo'
329        eq(dialog.result, 'foo')
330        eq(dialog.frame.result,'foo')
331
332
333class HelperTest(unittest.TestCase):
334    "Test module level helper functions."
335
336    def test_translate_key(self):
337        tr = config_key.translate_key
338        eq = self.assertEqual
339
340        # Letters return unchanged with no 'Shift'.
341        eq(tr('q', []), 'Key-q')
342        eq(tr('q', ['Control', 'Alt']), 'Key-q')
343
344        # 'Shift' uppercases single lowercase letters.
345        eq(tr('q', ['Shift']), 'Key-Q')
346        eq(tr('q', ['Control', 'Shift']), 'Key-Q')
347        eq(tr('q', ['Control', 'Alt', 'Shift']), 'Key-Q')
348
349        # Convert key name to keysym.
350        eq(tr('Page Up', []), 'Key-Prior')
351        # 'Shift' doesn't change case when it's not a single char.
352        eq(tr('*', ['Shift']), 'Key-asterisk')
353
354
355if __name__ == '__main__':
356    unittest.main(verbosity=2)
357