11"""Unit tests for cmd2/pt_utils.py"""
22
3- from typing import cast
3+ from typing import Any , cast
44from unittest .mock import Mock
55
66import pytest
77from prompt_toolkit .document import Document
88
99from cmd2 import pt_utils , utils
10+ from cmd2 .argparse_custom import CompletionItem
1011from cmd2 .history import HistoryItem
1112from cmd2 .parsing import Statement
1213
@@ -20,6 +21,8 @@ def __init__(self):
2021 self .history = []
2122 self .formatted_completions = ''
2223 self .completion_hint = ''
24+ self .statement_parser = Mock ()
25+ self .statement_parser .terminators = [';' ]
2326
2427
2528@pytest .fixture
@@ -30,16 +33,13 @@ def mock_cmd_app():
3033class TestCmd2Completer :
3134 def test_get_completions_basic (self , mock_cmd_app ):
3235 """Test basic completion without display matches."""
33- completer = pt_utils .Cmd2Completer (cast (any , mock_cmd_app ))
36+ completer = pt_utils .Cmd2Completer (cast (Any , mock_cmd_app ))
3437
3538 # Setup document
3639 text = "foo"
3740 line = "command foo"
3841 cursor_position = len (line )
39- document = Mock (spec = Document )
40- document .get_word_before_cursor .return_value = text
41- document .text = line
42- document .cursor_position = cursor_position
42+ document = Document (line , cursor_position = cursor_position )
4343
4444 # Setup matches
4545 mock_cmd_app .completion_matches = ["foobar" , "food" ]
@@ -65,15 +65,11 @@ def test_get_completions_basic(self, mock_cmd_app):
6565
6666 def test_get_completions_with_display_matches (self , mock_cmd_app ):
6767 """Test completion with display matches."""
68- completer = pt_utils .Cmd2Completer (cast (any , mock_cmd_app ))
68+ completer = pt_utils .Cmd2Completer (cast (Any , mock_cmd_app ))
6969
7070 # Setup document
71- text = "f"
7271 line = "f"
73- document = Mock (spec = Document )
74- document .get_word_before_cursor .return_value = text
75- document .text = line
76- document .cursor_position = 1
72+ document = Document (line , cursor_position = 1 )
7773
7874 # Setup matches
7975 mock_cmd_app .completion_matches = ["foo" , "bar" ]
@@ -92,12 +88,9 @@ def test_get_completions_with_display_matches(self, mock_cmd_app):
9288
9389 def test_get_completions_mismatched_display_matches (self , mock_cmd_app ):
9490 """Test completion when display_matches length doesn't match completion_matches."""
95- completer = pt_utils .Cmd2Completer (cast (any , mock_cmd_app ))
91+ completer = pt_utils .Cmd2Completer (cast (Any , mock_cmd_app ))
9692
97- document = Mock (spec = Document )
98- document .get_word_before_cursor .return_value = ""
99- document .text = ""
100- document .cursor_position = 0
93+ document = Document ("" , cursor_position = 0 )
10194
10295 mock_cmd_app .completion_matches = ["foo" , "bar" ]
10396 mock_cmd_app .display_matches = ["Foo Display" ] # Length mismatch
@@ -111,12 +104,9 @@ def test_get_completions_mismatched_display_matches(self, mock_cmd_app):
111104
112105 def test_get_completions_empty (self , mock_cmd_app ):
113106 """Test completion with no matches."""
114- completer = pt_utils .Cmd2Completer (cast (any , mock_cmd_app ))
107+ completer = pt_utils .Cmd2Completer (cast (Any , mock_cmd_app ))
115108
116- document = Mock (spec = Document )
117- document .get_word_before_cursor .return_value = ""
118- document .text = ""
119- document .cursor_position = 0
109+ document = Document ("" , cursor_position = 0 )
120110
121111 mock_cmd_app .completion_matches = []
122112
@@ -128,12 +118,9 @@ def test_init_with_custom_settings(self, mock_cmd_app):
128118 """Test initializing with custom settings."""
129119 mock_parser = Mock ()
130120 custom_settings = utils .CustomCompletionSettings (parser = mock_parser )
131- completer = pt_utils .Cmd2Completer (cast (any , mock_cmd_app ), custom_settings = custom_settings )
121+ completer = pt_utils .Cmd2Completer (cast (Any , mock_cmd_app ), custom_settings = custom_settings )
132122
133- document = Mock (spec = Document )
134- document .get_word_before_cursor .return_value = ""
135- document .text = ""
136- document .cursor_position = 0
123+ document = Document ("" , cursor_position = 0 )
137124
138125 mock_cmd_app .completion_matches = []
139126
@@ -142,6 +129,65 @@ def test_init_with_custom_settings(self, mock_cmd_app):
142129 mock_cmd_app .complete .assert_called_once ()
143130 assert mock_cmd_app .complete .call_args [1 ]['custom_settings' ] == custom_settings
144131
132+ def test_get_completions_with_hints (self , mock_cmd_app , monkeypatch ):
133+ """Test that hints and formatted completions are printed even with no matches."""
134+ mock_print = Mock ()
135+ monkeypatch .setattr (pt_utils , "print_formatted_text" , mock_print )
136+
137+ completer = pt_utils .Cmd2Completer (cast (Any , mock_cmd_app ))
138+ document = Document ("test" , cursor_position = 4 )
139+
140+ mock_cmd_app .formatted_completions = "Table Data"
141+ mock_cmd_app .completion_hint = "Hint Text"
142+ mock_cmd_app .completion_matches = []
143+
144+ list (completer .get_completions (document , None ))
145+
146+ assert mock_print .call_count == 2
147+ assert mock_cmd_app .formatted_completions == ""
148+ assert mock_cmd_app .completion_hint == ""
149+
150+ def test_get_completions_completion_item_meta (self , mock_cmd_app ):
151+ """Test that CompletionItem descriptive data is used as display_meta."""
152+ completer = pt_utils .Cmd2Completer (cast (Any , mock_cmd_app ))
153+ document = Document ("foo" , cursor_position = 3 )
154+
155+ # item1 with desc, item2 without desc
156+ item1 = CompletionItem ("foobar" , ["My Description" ])
157+ item2 = CompletionItem ("food" , [])
158+ mock_cmd_app .completion_matches = [item1 , item2 ]
159+
160+ completions = list (completer .get_completions (document , None ))
161+
162+ assert len (completions ) == 2
163+ assert completions [0 ].text == "foobar"
164+ # display_meta is converted to FormattedText
165+ assert completions [0 ].display_meta == [('' , 'My Description' )]
166+ assert completions [1 ].display_meta == [('' , '' )]
167+
168+ def test_get_completions_no_statement_parser (self , mock_cmd_app ):
169+ """Test initialization and completion without statement_parser."""
170+ del mock_cmd_app .statement_parser
171+ completer = pt_utils .Cmd2Completer (cast (Any , mock_cmd_app ))
172+
173+ document = Document ("foo bar" , cursor_position = 7 )
174+ list (completer .get_completions (document , None ))
175+
176+ # Should still work with default delimiters
177+ mock_cmd_app .complete .assert_called_once ()
178+
179+ def test_get_completions_custom_delimiters (self , mock_cmd_app ):
180+ """Test that custom delimiters (terminators) are respected."""
181+ mock_cmd_app .statement_parser .terminators = ['#' ]
182+ completer = pt_utils .Cmd2Completer (cast (Any , mock_cmd_app ))
183+
184+ # '#' should act as a word boundary
185+ document = Document ("cmd#arg" , cursor_position = 7 )
186+ list (completer .get_completions (document , None ))
187+
188+ # text should be "arg", begidx=4, endidx=7
189+ mock_cmd_app .complete .assert_called_with ("arg" , 0 , line = "cmd#arg" , begidx = 4 , endidx = 7 , custom_settings = None )
190+
145191
146192class TestCmd2History :
147193 def make_history_item (self , text ):
@@ -153,7 +199,7 @@ def make_history_item(self, text):
153199
154200 def test_load_history_strings (self , mock_cmd_app ):
155201 """Test loading history strings yields all items in forward order."""
156- history = pt_utils .Cmd2History (cast (any , mock_cmd_app ))
202+ history = pt_utils .Cmd2History (cast (Any , mock_cmd_app ))
157203
158204 # Setup history items
159205 # History in cmd2 is oldest to newest
@@ -172,7 +218,7 @@ def test_load_history_strings(self, mock_cmd_app):
172218
173219 def test_load_history_strings_empty (self , mock_cmd_app ):
174220 """Test loading history strings with empty history."""
175- history = pt_utils .Cmd2History (cast (any , mock_cmd_app ))
221+ history = pt_utils .Cmd2History (cast (Any , mock_cmd_app ))
176222
177223 mock_cmd_app .history = []
178224
@@ -182,7 +228,7 @@ def test_load_history_strings_empty(self, mock_cmd_app):
182228
183229 def test_get_strings (self , mock_cmd_app ):
184230 """Test get_strings returns deduped strings and does not cache."""
185- history = pt_utils .Cmd2History (cast (any , mock_cmd_app ))
231+ history = pt_utils .Cmd2History (cast (Any , mock_cmd_app ))
186232
187233 items = [
188234 self .make_history_item ("cmd1" ),
@@ -203,7 +249,7 @@ def test_get_strings(self, mock_cmd_app):
203249
204250 def test_store_string (self , mock_cmd_app ):
205251 """Test store_string does nothing."""
206- history = pt_utils .Cmd2History (cast (any , mock_cmd_app ))
252+ history = pt_utils .Cmd2History (cast (Any , mock_cmd_app ))
207253
208254 # Just ensure it doesn't raise error or modify cmd2 history
209255 history .store_string ("new command" )
0 commit comments