repl-toolkit 1.2.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,656 @@
1
+ """
2
+ Tests for completion utilities.
3
+ """
4
+
5
+ import os
6
+ import sys
7
+ from unittest.mock import Mock, patch
8
+
9
+ import pytest
10
+ from prompt_toolkit.document import Document
11
+
12
+ from repl_toolkit.completion import PrefixCompleter, ShellExpansionCompleter
13
+
14
+
15
+ def _get_text(formatted_text):
16
+ """Helper to extract plain text from FormattedText."""
17
+ if hasattr(formatted_text, "__iter__"):
18
+ return "".join(text for style, text in formatted_text)
19
+ return str(formatted_text)
20
+
21
+
22
+ class TestShellExpansionCompleter:
23
+ """Test ShellExpansionCompleter class."""
24
+
25
+ def setup_method(self):
26
+ """Set up test fixtures."""
27
+ self.completer = ShellExpansionCompleter()
28
+ self.complete_event = Mock()
29
+
30
+ def test_completer_initialization(self):
31
+ """Test completer initialization."""
32
+ completer = ShellExpansionCompleter()
33
+ assert completer.timeout == 2.0
34
+
35
+ completer = ShellExpansionCompleter(timeout=5.0)
36
+ assert completer.timeout == 5.0
37
+
38
+ def test_environment_variable_expansion(self):
39
+ """Test basic environment variable expansion."""
40
+ # Set test environment variable
41
+ os.environ["TEST_VAR"] = "test_value"
42
+
43
+ try:
44
+ # Cursor after variable pattern
45
+ document = Document(text="Hello ${TEST_VAR}", cursor_position=17)
46
+ completions = list(self.completer.get_completions(document, self.complete_event))
47
+
48
+ assert len(completions) == 1
49
+ assert completions[0].text == "test_value"
50
+ assert completions[0].start_position == -11 # Position to replace ${TEST_VAR}
51
+ assert "${TEST_VAR}" in _get_text(completions[0].display) or "TEST_VAR" in _get_text(
52
+ completions[0].display
53
+ )
54
+ assert "test_value" in str(completions[0].display)
55
+ finally:
56
+ del os.environ["TEST_VAR"]
57
+
58
+ def test_environment_variable_cursor_inside(self):
59
+ """Test expansion when cursor is inside the variable pattern."""
60
+ os.environ["USER_VAR"] = "inside_value"
61
+
62
+ try:
63
+ # Cursor in the middle of ${USER_VAR}
64
+ document = Document(text="Value: ${USER_VAR}", cursor_position=12)
65
+ completions = list(self.completer.get_completions(document, self.complete_event))
66
+
67
+ assert len(completions) == 1
68
+ assert completions[0].text == "inside_value"
69
+ finally:
70
+ del os.environ["USER_VAR"]
71
+
72
+ def test_environment_variable_not_found(self):
73
+ """Test behavior when environment variable doesn't exist."""
74
+ document = Document(text="${NONEXISTENT_VAR}", cursor_position=16)
75
+ completions = list(self.completer.get_completions(document, self.complete_event))
76
+
77
+ # Should not offer completion for nonexistent variables
78
+ assert len(completions) == 0
79
+
80
+ def test_environment_variable_cursor_outside(self):
81
+ """Test no completion when cursor is outside the pattern."""
82
+ os.environ["OUTSIDE_VAR"] = "value"
83
+
84
+ try:
85
+ # Cursor before the pattern
86
+ document = Document(text="Hello ${OUTSIDE_VAR}", cursor_position=0)
87
+ completions = list(self.completer.get_completions(document, self.complete_event))
88
+
89
+ # Should not complete when cursor is outside
90
+ assert len(completions) == 0
91
+ finally:
92
+ del os.environ["OUTSIDE_VAR"]
93
+
94
+ def test_multiple_environment_variables(self):
95
+ """Test multiple environment variables in same text."""
96
+ os.environ["VAR1"] = "value1"
97
+ os.environ["VAR2"] = "value2"
98
+
99
+ try:
100
+ # Cursor on second variable
101
+ document = Document(text="${VAR1} and ${VAR2}", cursor_position=19)
102
+ completions = list(self.completer.get_completions(document, self.complete_event))
103
+
104
+ assert len(completions) == 1
105
+ assert completions[0].text == "value2"
106
+ finally:
107
+ del os.environ["VAR1"]
108
+ del os.environ["VAR2"]
109
+
110
+ def test_command_execution_simple(self):
111
+ """Test simple command execution."""
112
+ # Use echo command which is available on most systems
113
+ document = Document(text="Result: $(echo test)", cursor_position=20)
114
+ completions = list(self.completer.get_completions(document, self.complete_event))
115
+
116
+ assert len(completions) == 1
117
+ assert completions[0].text == "test"
118
+ assert "$(echo test)" in str(completions[0].display)
119
+
120
+ def test_command_execution_cursor_inside(self):
121
+ """Test command execution when cursor is inside pattern."""
122
+ document = Document(text="$(echo hello)", cursor_position=8)
123
+ completions = list(self.completer.get_completions(document, self.complete_event))
124
+
125
+ assert len(completions) == 1
126
+ assert completions[0].text == "hello"
127
+
128
+ def test_command_execution_with_output_trimming(self):
129
+ """Test that command output is trimmed."""
130
+ document = Document(text='$(printf " spaces ")', cursor_position=22)
131
+ completions = list(self.completer.get_completions(document, self.complete_event))
132
+
133
+ assert len(completions) == 1
134
+ assert completions[0].text == "spaces"
135
+
136
+ def test_command_execution_empty_command(self):
137
+ """Test handling of empty command pattern."""
138
+ document = Document(text="Empty: $()", cursor_position=10)
139
+ completions = list(self.completer.get_completions(document, self.complete_event))
140
+
141
+ # Should not complete empty commands
142
+ assert len(completions) == 0
143
+
144
+ def test_command_execution_whitespace_only(self):
145
+ """Test handling of whitespace-only command."""
146
+ document = Document(text="$( )", cursor_position=6)
147
+ completions = list(self.completer.get_completions(document, self.complete_event))
148
+
149
+ # Should not complete whitespace-only commands
150
+ assert len(completions) == 0
151
+
152
+ def test_command_execution_not_found(self):
153
+ """Test handling of command not found error."""
154
+ document = Document(text="$(nonexistent_command_xyz)", cursor_position=26)
155
+ completions = list(self.completer.get_completions(document, self.complete_event))
156
+
157
+ # Should offer completion with error message
158
+ assert len(completions) == 1
159
+ assert completions[0].text == "" # Empty replacement
160
+ assert "Error" in str(completions[0].display)
161
+
162
+ def test_command_execution_failure(self):
163
+ """Test handling of command execution failure."""
164
+ # Command that will fail
165
+ document = Document(text="$(ls /nonexistent/path)", cursor_position=23)
166
+ completions = list(self.completer.get_completions(document, self.complete_event))
167
+
168
+ # Should offer completion with error message
169
+ assert len(completions) == 1
170
+ assert completions[0].text == ""
171
+ assert "Error" in str(completions[0].display)
172
+
173
+ def test_command_execution_timeout(self):
174
+ """Test command execution timeout."""
175
+ completer = ShellExpansionCompleter(timeout=0.1)
176
+
177
+ # Command that will timeout (sleep longer than timeout)
178
+ document = Document(text="$(sleep 5)", cursor_position=10)
179
+ completions = list(completer.get_completions(document, self.complete_event))
180
+
181
+ # Should offer completion with timeout message
182
+ assert len(completions) == 1
183
+ assert completions[0].text == ""
184
+ assert "Timeout" in str(completions[0].display)
185
+
186
+ def test_mixed_patterns(self):
187
+ """Test combination of environment variables and commands."""
188
+ os.environ["MIX_VAR"] = "envvalue"
189
+
190
+ try:
191
+ # Text with both patterns, cursor on command
192
+ document = Document(text="${MIX_VAR} and $(echo cmd)", cursor_position=26)
193
+ completions = list(self.completer.get_completions(document, self.complete_event))
194
+
195
+ # Should only complete the pattern at cursor
196
+ assert len(completions) == 1
197
+ assert completions[0].text == "cmd"
198
+ finally:
199
+ del os.environ["MIX_VAR"]
200
+
201
+ def test_cursor_outside_all_patterns(self):
202
+ """Test no completion when cursor is outside all patterns."""
203
+ os.environ["OUT_VAR"] = "value"
204
+
205
+ try:
206
+ # Cursor in plain text area
207
+ document = Document(text="Plain ${OUT_VAR} text $(echo test)", cursor_position=19)
208
+ completions = list(self.completer.get_completions(document, self.complete_event))
209
+
210
+ # Should not complete
211
+ assert len(completions) == 0
212
+ finally:
213
+ del os.environ["OUT_VAR"]
214
+
215
+ def test_variable_pattern_validation(self):
216
+ """Test that only valid variable names are matched."""
217
+ os.environ["_VALID_VAR"] = "valid"
218
+
219
+ try:
220
+ # Valid variable name
221
+ document = Document(text="${_VALID_VAR}", cursor_position=13)
222
+ completions = list(self.completer.get_completions(document, self.complete_event))
223
+ assert len(completions) == 1
224
+
225
+ # Invalid patterns should not be matched by regex
226
+ # (testing the pattern itself, not the completer behavior)
227
+ assert ShellExpansionCompleter.VAR_PATTERN.match("${123INVALID}") is None
228
+ assert ShellExpansionCompleter.VAR_PATTERN.match("${-INVALID}") is None
229
+ finally:
230
+ del os.environ["_VALID_VAR"]
231
+
232
+ def test_start_position_calculation(self):
233
+ """Test correct calculation of start_position."""
234
+ os.environ["POS_VAR"] = "replacement"
235
+
236
+ try:
237
+ text = "Prefix ${POS_VAR} suffix"
238
+ cursor_position = 17 # End of ${POS_VAR}
239
+ document = Document(text=text, cursor_position=cursor_position)
240
+
241
+ completions = list(self.completer.get_completions(document, self.complete_event))
242
+
243
+ assert len(completions) == 1
244
+ # start_position should be negative, pointing back to start of pattern
245
+ # Pattern starts at position 7, cursor at 17
246
+ # start_position = 7 - 17 = -10
247
+ assert completions[0].start_position == -10
248
+ finally:
249
+ del os.environ["POS_VAR"]
250
+
251
+ def test_completer_with_real_environment_variables(self):
252
+ """Test with actual system environment variables."""
253
+ # USER or USERNAME should exist on most systems
254
+ var_name = (
255
+ "USER" if "USER" in os.environ else "USERNAME" if "USERNAME" in os.environ else None
256
+ )
257
+
258
+ if var_name:
259
+ document = Document(text=f"${{{var_name}}}", cursor_position=len(var_name) + 3)
260
+ completions = list(self.completer.get_completions(document, self.complete_event))
261
+
262
+ assert len(completions) == 1
263
+ assert completions[0].text == os.environ[var_name]
264
+ else:
265
+ pytest.skip("No USER or USERNAME environment variable available")
266
+
267
+ def test_display_meta_fields(self):
268
+ """Test that display_meta fields are set correctly."""
269
+ os.environ["META_VAR"] = "value"
270
+
271
+ try:
272
+ # Test environment variable
273
+ document = Document(text="${META_VAR}", cursor_position=11)
274
+ completions = list(self.completer.get_completions(document, self.complete_event))
275
+ assert _get_text(completions[0].display_meta) == "Environment variable"
276
+
277
+ # Test command
278
+ document = Document(text="$(echo test)", cursor_position=12)
279
+ completions = list(self.completer.get_completions(document, self.complete_event))
280
+ assert _get_text(completions[0].display_meta) == "Shell command"
281
+ finally:
282
+ del os.environ["META_VAR"]
283
+
284
+ def test_patterns_are_non_greedy(self):
285
+ """Test that command pattern is non-greedy."""
286
+ # Multiple command patterns
287
+ document = Document(text="$(echo a) and $(echo b)", cursor_position=9)
288
+ completions = list(self.completer.get_completions(document, self.complete_event))
289
+
290
+ # Should only complete the first command where cursor is
291
+ assert len(completions) == 1
292
+ assert completions[0].text == "a"
293
+
294
+
295
+ class TestShellExpansionCompleterIntegration:
296
+ """Integration tests for ShellExpansionCompleter."""
297
+
298
+ def test_import_from_package(self):
299
+ """Test that completer can be imported from main package."""
300
+ from repl_toolkit import ShellExpansionCompleter
301
+
302
+ completer = ShellExpansionCompleter()
303
+ assert completer is not None
304
+
305
+ def test_use_with_merge_completers(self):
306
+ """Test integration with prompt_toolkit's merge_completers."""
307
+ from prompt_toolkit.completion import WordCompleter, merge_completers
308
+
309
+ from repl_toolkit import ShellExpansionCompleter
310
+
311
+ env_completer = ShellExpansionCompleter()
312
+ word_completer = WordCompleter(["exit", "help"])
313
+
314
+ combined = merge_completers([env_completer, word_completer])
315
+
316
+ assert combined is not None
317
+
318
+ def test_completer_protocol_compliance(self):
319
+ """Test that ShellExpansionCompleter implements Completer protocol."""
320
+ from prompt_toolkit.completion import Completer
321
+
322
+ from repl_toolkit import ShellExpansionCompleter
323
+
324
+ completer = ShellExpansionCompleter()
325
+
326
+ # Should have get_completions method
327
+ assert hasattr(completer, "get_completions")
328
+ assert callable(completer.get_completions)
329
+
330
+ # Should be instance of Completer
331
+ assert isinstance(completer, Completer)
332
+
333
+
334
+ class TestMultilineCommandCompletion:
335
+ """Test multi-line command output completion."""
336
+
337
+ def setup_method(self):
338
+ """Set up test fixtures."""
339
+ self.completer = ShellExpansionCompleter(multiline_all=True)
340
+ self.complete_event = Mock()
341
+
342
+ def test_single_line_command(self):
343
+ """Test command with single line output."""
344
+ document = Document(text="$(echo single)", cursor_position=14)
345
+ completions = list(self.completer.get_completions(document, self.complete_event))
346
+
347
+ # Should have one completion for single line
348
+ assert len(completions) == 1
349
+ assert completions[0].text == "single"
350
+ assert "single" in str(completions[0].display)
351
+
352
+ def test_multiline_command_with_all(self):
353
+ """Test command with multi-line output offers ALL + individual lines."""
354
+ # Command that produces multiple lines
355
+ document = Document(text='$(printf "line1\\nline2\\nline3")', cursor_position=31)
356
+ completions = list(self.completer.get_completions(document, self.complete_event))
357
+
358
+ # Should have ALL + 3 lines = 4 completions
359
+ assert len(completions) == 4
360
+
361
+ # First should be ALL
362
+ assert "ALL" in str(completions[0].display)
363
+ assert completions[0].text == "line1\nline2\nline3"
364
+
365
+ # Then individual lines
366
+ assert completions[1].text == "line1"
367
+ assert "Line 1" in str(completions[1].display)
368
+
369
+ assert completions[2].text == "line2"
370
+ assert "Line 2" in str(completions[2].display)
371
+
372
+ assert completions[3].text == "line3"
373
+ assert "Line 3" in str(completions[3].display)
374
+
375
+ def test_multiline_command_without_all(self):
376
+ """Test multi-line output without ALL option."""
377
+ completer = ShellExpansionCompleter(multiline_all=False)
378
+
379
+ document = Document(text='$(printf "a\\nb\\nc")', cursor_position=19)
380
+ completions = list(completer.get_completions(document, self.complete_event))
381
+
382
+ # Should have only 3 lines (no ALL)
383
+ assert len(completions) == 3
384
+
385
+ # All should be individual lines
386
+ assert completions[0].text == "a"
387
+ assert completions[1].text == "b"
388
+ assert completions[2].text == "c"
389
+
390
+ # None should be ALL
391
+ for comp in completions:
392
+ assert "ALL" not in str(comp.display)
393
+
394
+ def test_command_with_empty_lines(self):
395
+ """Test that empty lines are filtered out."""
396
+ document = Document(text='$(printf "a\\n\\nb\\n\\n")', cursor_position=22)
397
+ completions = list(self.completer.get_completions(document, self.complete_event))
398
+
399
+ # Should have ALL + 2 non-empty lines = 3 completions
400
+ assert len(completions) == 3
401
+
402
+ # ALL contains original with empty lines
403
+ assert "ALL" in str(completions[0].display)
404
+
405
+ # But individual lines skip empty ones
406
+ assert completions[1].text == "a"
407
+ assert completions[2].text == "b"
408
+
409
+ def test_command_no_output(self):
410
+ """Test command with no output."""
411
+ # Command that produces no output
412
+ document = Document(text="$(true)", cursor_position=7)
413
+ completions = list(self.completer.get_completions(document, self.complete_event))
414
+
415
+ # Should have one completion indicating no output
416
+ assert len(completions) == 1
417
+ assert completions[0].text == ""
418
+ assert "no output" in str(completions[0].display)
419
+
420
+ def test_multiline_preserves_order(self):
421
+ """Test that lines are offered in order."""
422
+ document = Document(text='$(printf "first\\nsecond\\nthird")', cursor_position=32)
423
+ completions = list(self.completer.get_completions(document, self.complete_event))
424
+
425
+ # Skip ALL, check line order
426
+ assert completions[1].text == "first"
427
+ assert completions[2].text == "second"
428
+ assert completions[3].text == "third"
429
+
430
+
431
+ class TestPrefixCompleter:
432
+ """Test PrefixCompleter class."""
433
+
434
+ def setup_method(self):
435
+ """Set up test fixtures."""
436
+ self.completer = PrefixCompleter(["/help", "/exit", "/quit"], prefix="/")
437
+ self.complete_event = Mock()
438
+
439
+ def test_command_at_start_of_line(self):
440
+ """Test command completion at start of line."""
441
+ document = Document(text="/he", cursor_position=3)
442
+ completions = list(self.completer.get_completions(document, self.complete_event))
443
+
444
+ assert len(completions) == 1
445
+ assert completions[0].text == "/help"
446
+ assert completions[0].start_position == -3
447
+
448
+ def test_command_after_whitespace(self):
449
+ """Test command completion after whitespace."""
450
+ document = Document(text="some text /he", cursor_position=13)
451
+ completions = list(self.completer.get_completions(document, self.complete_event))
452
+
453
+ assert len(completions) == 1
454
+ assert completions[0].text == "/help"
455
+
456
+ def test_no_completion_in_middle_of_text(self):
457
+ """Test no completion when / is in middle of word."""
458
+ document = Document(text="path/to/file", cursor_position=12)
459
+ completions = list(self.completer.get_completions(document, self.complete_event))
460
+
461
+ # Should not complete file paths
462
+ assert len(completions) == 0
463
+
464
+ def test_multiple_matching_commands(self):
465
+ """Test multiple matching commands."""
466
+ document = Document(text="/e", cursor_position=2)
467
+ completions = list(self.completer.get_completions(document, self.complete_event))
468
+
469
+ # Should match /exit (not /help or /quit)
470
+ assert len(completions) == 1
471
+ assert completions[0].text == "/exit"
472
+
473
+ def test_exact_match(self):
474
+ """Test exact command match."""
475
+ document = Document(text="/help", cursor_position=5)
476
+ completions = list(self.completer.get_completions(document, self.complete_event))
477
+
478
+ # Should still offer completion even for exact match
479
+ assert len(completions) == 1
480
+ assert completions[0].text == "/help"
481
+
482
+ def test_no_match(self):
483
+ """Test no matching commands."""
484
+ document = Document(text="/xyz", cursor_position=4)
485
+ completions = list(self.completer.get_completions(document, self.complete_event))
486
+
487
+ assert len(completions) == 0
488
+
489
+ def test_case_insensitive(self):
490
+ """Test case-insensitive matching."""
491
+ completer = PrefixCompleter(["/Help", "/Exit"], ignore_case=True)
492
+
493
+ document = Document(text="/he", cursor_position=3)
494
+ completions = list(completer.get_completions(document, self.complete_event))
495
+
496
+ assert len(completions) == 1
497
+ assert completions[0].text == "/Help"
498
+
499
+ def test_case_sensitive(self):
500
+ """Test case-sensitive matching."""
501
+ completer = PrefixCompleter(["/Help", "/Exit"], ignore_case=False)
502
+
503
+ document = Document(text="/he", cursor_position=3)
504
+ completions = list(completer.get_completions(document, self.complete_event))
505
+
506
+ # Should not match /Help (capital H)
507
+ assert len(completions) == 0
508
+
509
+ def test_commands_without_leading_slash(self):
510
+ """Test that commands without / get it added."""
511
+ completer = PrefixCompleter(["help", "exit"], prefix="/")
512
+
513
+ document = Document(text="/he", cursor_position=3)
514
+ completions = list(completer.get_completions(document, self.complete_event))
515
+
516
+ assert len(completions) == 1
517
+ assert completions[0].text == "/help"
518
+
519
+ def test_command_after_newline(self):
520
+ """Test command completion after newline."""
521
+ document = Document(text="line1\n/he", cursor_position=9)
522
+ completions = list(self.completer.get_completions(document, self.complete_event))
523
+
524
+ assert len(completions) == 1
525
+ assert completions[0].text == "/help"
526
+
527
+ def test_import_from_package(self):
528
+ """Test that PrefixCompleter can be imported."""
529
+ from repl_toolkit import PrefixCompleter
530
+
531
+ completer = PrefixCompleter(["/test"])
532
+ assert completer is not None
533
+
534
+
535
+ class TestShellExpansionCompleterLimits:
536
+ """Test limit features of ShellExpansionCompleter."""
537
+
538
+ def setup_method(self):
539
+ """Set up test fixtures."""
540
+ self.complete_event = Mock()
541
+
542
+ def test_default_limits(self):
543
+ """Test default limit values."""
544
+ completer = ShellExpansionCompleter()
545
+ assert completer.max_lines == 50
546
+ assert completer.max_display_length == 80
547
+
548
+ def test_line_limit_enforcement(self):
549
+ """Test that max_lines limit is enforced in menu."""
550
+ completer = ShellExpansionCompleter(max_lines=5)
551
+
552
+ # Command that produces 10 lines
553
+ cmd = 'printf "1\\n2\\n3\\n4\\n5\\n6\\n7\\n8\\n9\\n10"'
554
+ document = Document(text=f"$({cmd})", cursor_position=len(f"$({cmd})"))
555
+
556
+ completions = list(completer.get_completions(document, self.complete_event))
557
+
558
+ # Should have: ALL + 5 lines + "more lines" indicator = 7
559
+ assert len(completions) == 7
560
+
561
+ # ALL should have full output
562
+ all_comp = completions[0]
563
+ assert "10" in all_comp.text
564
+ assert "ALL (10 lines)" in str(all_comp.display)
565
+
566
+ # Check individual lines shown (should be 5)
567
+ line_comps = [c for c in completions[1:] if "Line " in str(c.display) and c.text]
568
+ assert len(line_comps) == 5
569
+
570
+ # Check "more lines" indicator
571
+ more_indicator = [c for c in completions if "more lines" in str(c.display)]
572
+ assert len(more_indicator) == 1
573
+ assert "5 more lines" in str(more_indicator[0].display)
574
+
575
+ def test_display_length_truncation(self):
576
+ """Test that display is truncated but completion text is full."""
577
+ completer = ShellExpansionCompleter(max_display_length=20)
578
+
579
+ # Command with long output
580
+ long_text = "A" * 100
581
+ cmd = f'printf "{long_text}"'
582
+ document = Document(text=f"$({cmd})", cursor_position=len(f"$({cmd})"))
583
+
584
+ completions = list(completer.get_completions(document, self.complete_event))
585
+
586
+ assert len(completions) == 1
587
+ completion = completions[0]
588
+
589
+ # Full text in completion
590
+ assert len(completion.text) == 100
591
+ assert completion.text == long_text
592
+
593
+ # Truncated display
594
+ assert "..." in str(completion.display)
595
+
596
+ def test_env_variable_display_truncation(self):
597
+ """Test display truncation for long environment variables."""
598
+ long_value = "X" * 100
599
+ os.environ["TEST_LONG_VAR"] = long_value
600
+
601
+ try:
602
+ completer = ShellExpansionCompleter(max_display_length=25)
603
+
604
+ document = Document(text="${TEST_LONG_VAR}", cursor_position=16)
605
+ completions = list(completer.get_completions(document, self.complete_event))
606
+
607
+ assert len(completions) == 1
608
+ completion = completions[0]
609
+
610
+ # Full value in completion
611
+ assert len(completion.text) == 100
612
+ assert completion.text == long_value
613
+
614
+ # Truncated in display
615
+ assert "..." in str(completion.display)
616
+ finally:
617
+ del os.environ["TEST_LONG_VAR"]
618
+
619
+ def test_combined_limits(self):
620
+ """Test combination of line limit and display length limit."""
621
+ completer = ShellExpansionCompleter(max_lines=3, max_display_length=30)
622
+
623
+ # Command with 5 long lines
624
+ long_line = "B" * 60
625
+ cmd = f'printf "{long_line}\\n{long_line}\\n{long_line}\\n{long_line}\\n{long_line}"'
626
+ document = Document(text=f"$({cmd})", cursor_position=len(f"$({cmd})"))
627
+
628
+ completions = list(completer.get_completions(document, self.complete_event))
629
+
630
+ # ALL + 3 lines + "more lines" indicator = 5
631
+ assert len(completions) == 5
632
+
633
+ # ALL has full content
634
+ all_comp = completions[0]
635
+ assert long_line in all_comp.text
636
+
637
+ # Check individual lines
638
+ line_comps = [c for c in completions[1:] if "Line " in str(c.display) and c.text]
639
+ assert len(line_comps) == 3
640
+
641
+ # Each line should have full text but truncated display
642
+ for comp in line_comps:
643
+ assert len(comp.text) == 60
644
+ assert "..." in str(comp.display)
645
+
646
+ def test_no_truncation_when_under_limits(self):
647
+ """Test that short content is not truncated."""
648
+ completer = ShellExpansionCompleter(max_lines=10, max_display_length=50)
649
+
650
+ # Short command output
651
+ document = Document(text='$(printf "short")', cursor_position=16)
652
+ completions = list(completer.get_completions(document, self.complete_event))
653
+
654
+ assert len(completions) == 1
655
+ assert "..." not in str(completions[0].display)
656
+ assert completions[0].text == "short"