##// END OF EJS Templates
Add test for no matching shortcut for modification
krassowski -
Show More
@@ -1,450 +1,461
1 1 import pytest
2 2 from IPython.terminal.shortcuts.auto_suggest import (
3 3 accept,
4 4 accept_in_vi_insert_mode,
5 5 accept_token,
6 6 accept_character,
7 7 accept_word,
8 8 accept_and_keep_cursor,
9 9 discard,
10 10 NavigableAutoSuggestFromHistory,
11 11 swap_autosuggestion_up,
12 12 swap_autosuggestion_down,
13 13 )
14 14 from IPython.terminal.shortcuts.auto_match import skip_over
15 15 from IPython.terminal.shortcuts import create_ipython_shortcuts
16 16
17 17 from prompt_toolkit.history import InMemoryHistory
18 18 from prompt_toolkit.buffer import Buffer
19 19 from prompt_toolkit.document import Document
20 20 from prompt_toolkit.auto_suggest import AutoSuggestFromHistory
21 21
22 22 from unittest.mock import patch, Mock
23 23
24 24
25 25 def make_event(text, cursor, suggestion):
26 26 event = Mock()
27 27 event.current_buffer = Mock()
28 28 event.current_buffer.suggestion = Mock()
29 29 event.current_buffer.text = text
30 30 event.current_buffer.cursor_position = cursor
31 31 event.current_buffer.suggestion.text = suggestion
32 32 event.current_buffer.document = Document(text=text, cursor_position=cursor)
33 33 return event
34 34
35 35
36 36 @pytest.mark.parametrize(
37 37 "text, suggestion, expected",
38 38 [
39 39 ("", "def out(tag: str, n=50):", "def out(tag: str, n=50):"),
40 40 ("def ", "out(tag: str, n=50):", "out(tag: str, n=50):"),
41 41 ],
42 42 )
43 43 def test_accept(text, suggestion, expected):
44 44 event = make_event(text, len(text), suggestion)
45 45 buffer = event.current_buffer
46 46 buffer.insert_text = Mock()
47 47 accept(event)
48 48 assert buffer.insert_text.called
49 49 assert buffer.insert_text.call_args[0] == (expected,)
50 50
51 51
52 52 @pytest.mark.parametrize(
53 53 "text, suggestion",
54 54 [
55 55 ("", "def out(tag: str, n=50):"),
56 56 ("def ", "out(tag: str, n=50):"),
57 57 ],
58 58 )
59 59 def test_discard(text, suggestion):
60 60 event = make_event(text, len(text), suggestion)
61 61 buffer = event.current_buffer
62 62 buffer.insert_text = Mock()
63 63 discard(event)
64 64 assert not buffer.insert_text.called
65 65 assert buffer.suggestion is None
66 66
67 67
68 68 @pytest.mark.parametrize(
69 69 "text, cursor, suggestion, called",
70 70 [
71 71 ("123456", 6, "123456789", True),
72 72 ("123456", 3, "123456789", False),
73 73 ("123456 \n789", 6, "123456789", True),
74 74 ],
75 75 )
76 76 def test_autosuggest_at_EOL(text, cursor, suggestion, called):
77 77 """
78 78 test that autosuggest is only applied at end of line.
79 79 """
80 80
81 81 event = make_event(text, cursor, suggestion)
82 82 event.current_buffer.insert_text = Mock()
83 83 accept_in_vi_insert_mode(event)
84 84 if called:
85 85 event.current_buffer.insert_text.assert_called()
86 86 else:
87 87 event.current_buffer.insert_text.assert_not_called()
88 88 # event.current_buffer.document.get_end_of_line_position.assert_called()
89 89
90 90
91 91 @pytest.mark.parametrize(
92 92 "text, suggestion, expected",
93 93 [
94 94 ("", "def out(tag: str, n=50):", "def "),
95 95 ("d", "ef out(tag: str, n=50):", "ef "),
96 96 ("de ", "f out(tag: str, n=50):", "f "),
97 97 ("def", " out(tag: str, n=50):", " "),
98 98 ("def ", "out(tag: str, n=50):", "out("),
99 99 ("def o", "ut(tag: str, n=50):", "ut("),
100 100 ("def ou", "t(tag: str, n=50):", "t("),
101 101 ("def out", "(tag: str, n=50):", "("),
102 102 ("def out(", "tag: str, n=50):", "tag: "),
103 103 ("def out(t", "ag: str, n=50):", "ag: "),
104 104 ("def out(ta", "g: str, n=50):", "g: "),
105 105 ("def out(tag", ": str, n=50):", ": "),
106 106 ("def out(tag:", " str, n=50):", " "),
107 107 ("def out(tag: ", "str, n=50):", "str, "),
108 108 ("def out(tag: s", "tr, n=50):", "tr, "),
109 109 ("def out(tag: st", "r, n=50):", "r, "),
110 110 ("def out(tag: str", ", n=50):", ", n"),
111 111 ("def out(tag: str,", " n=50):", " n"),
112 112 ("def out(tag: str, ", "n=50):", "n="),
113 113 ("def out(tag: str, n", "=50):", "="),
114 114 ("def out(tag: str, n=", "50):", "50)"),
115 115 ("def out(tag: str, n=5", "0):", "0)"),
116 116 ("def out(tag: str, n=50", "):", "):"),
117 117 ("def out(tag: str, n=50)", ":", ":"),
118 118 ],
119 119 )
120 120 def test_autosuggest_token(text, suggestion, expected):
121 121 event = make_event(text, len(text), suggestion)
122 122 event.current_buffer.insert_text = Mock()
123 123 accept_token(event)
124 124 assert event.current_buffer.insert_text.called
125 125 assert event.current_buffer.insert_text.call_args[0] == (expected,)
126 126
127 127
128 128 @pytest.mark.parametrize(
129 129 "text, suggestion, expected",
130 130 [
131 131 ("", "def out(tag: str, n=50):", "d"),
132 132 ("d", "ef out(tag: str, n=50):", "e"),
133 133 ("de ", "f out(tag: str, n=50):", "f"),
134 134 ("def", " out(tag: str, n=50):", " "),
135 135 ],
136 136 )
137 137 def test_accept_character(text, suggestion, expected):
138 138 event = make_event(text, len(text), suggestion)
139 139 event.current_buffer.insert_text = Mock()
140 140 accept_character(event)
141 141 assert event.current_buffer.insert_text.called
142 142 assert event.current_buffer.insert_text.call_args[0] == (expected,)
143 143
144 144
145 145 @pytest.mark.parametrize(
146 146 "text, suggestion, expected",
147 147 [
148 148 ("", "def out(tag: str, n=50):", "def "),
149 149 ("d", "ef out(tag: str, n=50):", "ef "),
150 150 ("de", "f out(tag: str, n=50):", "f "),
151 151 ("def", " out(tag: str, n=50):", " "),
152 152 # (this is why we also have accept_token)
153 153 ("def ", "out(tag: str, n=50):", "out(tag: "),
154 154 ],
155 155 )
156 156 def test_accept_word(text, suggestion, expected):
157 157 event = make_event(text, len(text), suggestion)
158 158 event.current_buffer.insert_text = Mock()
159 159 accept_word(event)
160 160 assert event.current_buffer.insert_text.called
161 161 assert event.current_buffer.insert_text.call_args[0] == (expected,)
162 162
163 163
164 164 @pytest.mark.parametrize(
165 165 "text, suggestion, expected, cursor",
166 166 [
167 167 ("", "def out(tag: str, n=50):", "def out(tag: str, n=50):", 0),
168 168 ("def ", "out(tag: str, n=50):", "out(tag: str, n=50):", 4),
169 169 ],
170 170 )
171 171 def test_accept_and_keep_cursor(text, suggestion, expected, cursor):
172 172 event = make_event(text, cursor, suggestion)
173 173 buffer = event.current_buffer
174 174 buffer.insert_text = Mock()
175 175 accept_and_keep_cursor(event)
176 176 assert buffer.insert_text.called
177 177 assert buffer.insert_text.call_args[0] == (expected,)
178 178 assert buffer.cursor_position == cursor
179 179
180 180
181 181 def test_autosuggest_token_empty():
182 182 full = "def out(tag: str, n=50):"
183 183 event = make_event(full, len(full), "")
184 184 event.current_buffer.insert_text = Mock()
185 185
186 186 with patch(
187 187 "prompt_toolkit.key_binding.bindings.named_commands.forward_word"
188 188 ) as forward_word:
189 189 accept_token(event)
190 190 assert not event.current_buffer.insert_text.called
191 191 assert forward_word.called
192 192
193 193
194 194 def test_other_providers():
195 195 """Ensure that swapping autosuggestions does not break with other providers"""
196 196 provider = AutoSuggestFromHistory()
197 197 ip = get_ipython()
198 198 ip.auto_suggest = provider
199 199 event = Mock()
200 200 event.current_buffer = Buffer()
201 201 assert swap_autosuggestion_up(event) is None
202 202 assert swap_autosuggestion_down(event) is None
203 203
204 204
205 205 async def test_navigable_provider():
206 206 provider = NavigableAutoSuggestFromHistory()
207 207 history = InMemoryHistory(history_strings=["very_a", "very", "very_b", "very_c"])
208 208 buffer = Buffer(history=history)
209 209 ip = get_ipython()
210 210 ip.auto_suggest = provider
211 211
212 212 async for _ in history.load():
213 213 pass
214 214
215 215 buffer.cursor_position = 5
216 216 buffer.text = "very"
217 217
218 218 up = swap_autosuggestion_up
219 219 down = swap_autosuggestion_down
220 220
221 221 event = Mock()
222 222 event.current_buffer = buffer
223 223
224 224 def get_suggestion():
225 225 suggestion = provider.get_suggestion(buffer, buffer.document)
226 226 buffer.suggestion = suggestion
227 227 return suggestion
228 228
229 229 assert get_suggestion().text == "_c"
230 230
231 231 # should go up
232 232 up(event)
233 233 assert get_suggestion().text == "_b"
234 234
235 235 # should skip over 'very' which is identical to buffer content
236 236 up(event)
237 237 assert get_suggestion().text == "_a"
238 238
239 239 # should cycle back to beginning
240 240 up(event)
241 241 assert get_suggestion().text == "_c"
242 242
243 243 # should cycle back through end boundary
244 244 down(event)
245 245 assert get_suggestion().text == "_a"
246 246
247 247 down(event)
248 248 assert get_suggestion().text == "_b"
249 249
250 250 down(event)
251 251 assert get_suggestion().text == "_c"
252 252
253 253 down(event)
254 254 assert get_suggestion().text == "_a"
255 255
256 256
257 257 async def test_navigable_provider_multiline_entries():
258 258 provider = NavigableAutoSuggestFromHistory()
259 259 history = InMemoryHistory(history_strings=["very_a\nvery_b", "very_c"])
260 260 buffer = Buffer(history=history)
261 261 ip = get_ipython()
262 262 ip.auto_suggest = provider
263 263
264 264 async for _ in history.load():
265 265 pass
266 266
267 267 buffer.cursor_position = 5
268 268 buffer.text = "very"
269 269 up = swap_autosuggestion_up
270 270 down = swap_autosuggestion_down
271 271
272 272 event = Mock()
273 273 event.current_buffer = buffer
274 274
275 275 def get_suggestion():
276 276 suggestion = provider.get_suggestion(buffer, buffer.document)
277 277 buffer.suggestion = suggestion
278 278 return suggestion
279 279
280 280 assert get_suggestion().text == "_c"
281 281
282 282 up(event)
283 283 assert get_suggestion().text == "_b"
284 284
285 285 up(event)
286 286 assert get_suggestion().text == "_a"
287 287
288 288 down(event)
289 289 assert get_suggestion().text == "_b"
290 290
291 291 down(event)
292 292 assert get_suggestion().text == "_c"
293 293
294 294
295 295 def create_session_mock():
296 296 session = Mock()
297 297 session.default_buffer = Buffer()
298 298 return session
299 299
300 300
301 301 def test_navigable_provider_connection():
302 302 provider = NavigableAutoSuggestFromHistory()
303 303 provider.skip_lines = 1
304 304
305 305 session_1 = create_session_mock()
306 306 provider.connect(session_1)
307 307
308 308 assert provider.skip_lines == 1
309 309 session_1.default_buffer.on_text_insert.fire()
310 310 assert provider.skip_lines == 0
311 311
312 312 session_2 = create_session_mock()
313 313 provider.connect(session_2)
314 314 provider.skip_lines = 2
315 315
316 316 assert provider.skip_lines == 2
317 317 session_2.default_buffer.on_text_insert.fire()
318 318 assert provider.skip_lines == 0
319 319
320 320 provider.skip_lines = 3
321 321 provider.disconnect()
322 322 session_1.default_buffer.on_text_insert.fire()
323 323 session_2.default_buffer.on_text_insert.fire()
324 324 assert provider.skip_lines == 3
325 325
326 326
327 327 @pytest.fixture
328 328 def ipython_with_prompt():
329 329 ip = get_ipython()
330 330 ip.pt_app = Mock()
331 331 ip.pt_app.key_bindings = create_ipython_shortcuts(ip)
332 332 try:
333 333 yield ip
334 334 finally:
335 335 ip.pt_app = None
336 336
337 337
338 338 def find_bindings_by_command(command):
339 339 ip = get_ipython()
340 340 return [
341 341 binding
342 342 for binding in ip.pt_app.key_bindings.bindings
343 343 if binding.handler == command
344 344 ]
345 345
346 346
347 347 def test_modify_unique_shortcut(ipython_with_prompt):
348 348 original = find_bindings_by_command(accept_token)
349 349 assert len(original) == 1
350 350
351 351 ipython_with_prompt.shortcuts = [
352 352 {"command": "IPython:auto_suggest.accept_token", "new_keys": ["a", "b", "c"]}
353 353 ]
354 354 matched = find_bindings_by_command(accept_token)
355 355 assert len(matched) == 1
356 356 assert list(matched[0].keys) == ["a", "b", "c"]
357 357 assert list(matched[0].keys) != list(original[0].keys)
358 358 assert matched[0].filter == original[0].filter
359 359
360 360 ipython_with_prompt.shortcuts = [
361 361 {"command": "IPython:auto_suggest.accept_token", "new_filter": "always"}
362 362 ]
363 363 matched = find_bindings_by_command(accept_token)
364 364 assert len(matched) == 1
365 365 assert list(matched[0].keys) != ["a", "b", "c"]
366 366 assert list(matched[0].keys) == list(original[0].keys)
367 367 assert matched[0].filter != original[0].filter
368 368
369 369
370 370 def test_disable_shortcut(ipython_with_prompt):
371 371 matched = find_bindings_by_command(accept_token)
372 372 assert len(matched) == 1
373 373
374 374 ipython_with_prompt.shortcuts = [
375 375 {"command": "IPython:auto_suggest.accept_token", "new_keys": []}
376 376 ]
377 377 matched = find_bindings_by_command(accept_token)
378 378 assert len(matched) == 0
379 379
380 380 ipython_with_prompt.shortcuts = []
381 381 matched = find_bindings_by_command(accept_token)
382 382 assert len(matched) == 1
383 383
384 384
385 385 def test_modify_shortcut_with_filters(ipython_with_prompt):
386 386 matched = find_bindings_by_command(skip_over)
387 387 matched_keys = {m.keys[0] for m in matched}
388 388 assert matched_keys == {")", "]", "}", "'", '"'}
389 389
390 390 with pytest.raises(ValueError, match="Multiple shortcuts matching"):
391 391 ipython_with_prompt.shortcuts = [
392 392 {"command": "IPython:auto_match.skip_over", "new_keys": ["x"]}
393 393 ]
394 394
395 395 ipython_with_prompt.shortcuts = [
396 396 {
397 397 "command": "IPython:auto_match.skip_over",
398 398 "new_keys": ["x"],
399 399 "match_filter": "focused_insert & auto_match & followed_by_single_quote",
400 400 }
401 401 ]
402 402 matched = find_bindings_by_command(skip_over)
403 403 matched_keys = {m.keys[0] for m in matched}
404 404 assert matched_keys == {")", "]", "}", "x", '"'}
405 405
406 406
407 407 def example_command():
408 408 pass
409 409
410 410
411 411 def test_add_shortcut_for_new_command(ipython_with_prompt):
412 412 matched = find_bindings_by_command(example_command)
413 413 assert len(matched) == 0
414 414
415 415 with pytest.raises(ValueError, match="example_command is not a known"):
416 416 ipython_with_prompt.shortcuts = [
417 417 {"command": "example_command", "new_keys": ["x"]}
418 418 ]
419 419 matched = find_bindings_by_command(example_command)
420 420 assert len(matched) == 0
421 421
422 422
423 def test_modify_shortcut_failure(ipython_with_prompt):
424 with pytest.raises(ValueError, match="No shortcuts matching"):
425 ipython_with_prompt.shortcuts = [
426 {
427 "command": "IPython:auto_match.skip_over",
428 "match_keys": ["x"],
429 "new_keys": ["y"],
430 }
431 ]
432
433
423 434 def test_add_shortcut_for_existing_command(ipython_with_prompt):
424 435 matched = find_bindings_by_command(skip_over)
425 436 assert len(matched) == 5
426 437
427 438 with pytest.raises(ValueError, match="Cannot add a shortcut without keys"):
428 439 ipython_with_prompt.shortcuts = [
429 440 {"command": "IPython:auto_match.skip_over", "new_keys": [], "create": True}
430 441 ]
431 442
432 443 ipython_with_prompt.shortcuts = [
433 444 {"command": "IPython:auto_match.skip_over", "new_keys": ["x"], "create": True}
434 445 ]
435 446 matched = find_bindings_by_command(skip_over)
436 447 assert len(matched) == 6
437 448
438 449 ipython_with_prompt.shortcuts = []
439 450 matched = find_bindings_by_command(skip_over)
440 451 assert len(matched) == 5
441 452
442 453
443 454 def test_setting_shortcuts_before_pt_app_init():
444 455 ipython = get_ipython()
445 456 assert ipython.pt_app is None
446 457 shortcuts = [
447 458 {"command": "IPython:auto_match.skip_over", "new_keys": ["x"], "create": True}
448 459 ]
449 460 ipython.shortcuts = shortcuts
450 461 assert ipython.shortcuts == shortcuts
General Comments 0
You need to be logged in to leave comments. Login now