Skip to content

Commit 798d400

Browse files
committed
Added test_pt_utils.py with unit tests to fully cover code in cmd2/pt_utils.py
1 parent 100a98f commit 798d400

File tree

1 file changed

+210
-0
lines changed

1 file changed

+210
-0
lines changed

tests/test_pt_utils.py

Lines changed: 210 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,210 @@
1+
"""Unit tests for cmd2/pt_utils.py"""
2+
3+
from typing import cast
4+
from unittest.mock import Mock
5+
6+
import pytest
7+
from prompt_toolkit.document import Document
8+
9+
from cmd2 import pt_utils, utils
10+
from cmd2.history import HistoryItem
11+
from cmd2.parsing import Statement
12+
13+
14+
# Mock for cmd2.Cmd
15+
class MockCmd:
16+
def __init__(self):
17+
self.complete = Mock()
18+
self.completion_matches = []
19+
self.display_matches = []
20+
self.history = []
21+
22+
23+
@pytest.fixture
24+
def mock_cmd_app():
25+
return MockCmd()
26+
27+
28+
class TestCmd2Completer:
29+
def test_get_completions_basic(self, mock_cmd_app):
30+
"""Test basic completion without display matches."""
31+
completer = pt_utils.Cmd2Completer(cast(any, mock_cmd_app))
32+
33+
# Setup document
34+
text = "foo"
35+
line = "command foo"
36+
cursor_position = len(line)
37+
document = Mock(spec=Document)
38+
document.get_word_before_cursor.return_value = text
39+
document.text = line
40+
document.cursor_position = cursor_position
41+
42+
# Setup matches
43+
mock_cmd_app.completion_matches = ["foobar", "food"]
44+
mock_cmd_app.display_matches = [] # Empty means use completion matches for display
45+
46+
# Call get_completions
47+
completions = list(completer.get_completions(document, None))
48+
49+
# Verify cmd_app.complete was called correctly
50+
# begidx = cursor_position - len(text) = 11 - 3 = 8
51+
mock_cmd_app.complete.assert_called_once_with(text, 0, line=line, begidx=8, endidx=11, custom_settings=None)
52+
53+
# Verify completions
54+
assert len(completions) == 2
55+
assert completions[0].text == "foobar"
56+
assert completions[0].start_position == -3
57+
# prompt_toolkit 3.0+ uses FormattedText for display
58+
assert completions[0].display == [('', 'foobar')]
59+
60+
assert completions[1].text == "food"
61+
assert completions[1].start_position == -3
62+
assert completions[1].display == [('', 'food')]
63+
64+
def test_get_completions_with_display_matches(self, mock_cmd_app):
65+
"""Test completion with display matches."""
66+
completer = pt_utils.Cmd2Completer(cast(any, mock_cmd_app))
67+
68+
# Setup document
69+
text = "f"
70+
line = "f"
71+
document = Mock(spec=Document)
72+
document.get_word_before_cursor.return_value = text
73+
document.text = line
74+
document.cursor_position = 1
75+
76+
# Setup matches
77+
mock_cmd_app.completion_matches = ["foo", "bar"]
78+
mock_cmd_app.display_matches = ["Foo Display", "Bar Display"]
79+
80+
# Call get_completions
81+
completions = list(completer.get_completions(document, None))
82+
83+
# Verify completions
84+
assert len(completions) == 2
85+
assert completions[0].text == "foo"
86+
assert completions[0].display == [('', 'Foo Display')]
87+
88+
assert completions[1].text == "bar"
89+
assert completions[1].display == [('', 'Bar Display')]
90+
91+
def test_get_completions_mismatched_display_matches(self, mock_cmd_app):
92+
"""Test completion when display_matches length doesn't match completion_matches."""
93+
completer = pt_utils.Cmd2Completer(cast(any, mock_cmd_app))
94+
95+
document = Mock(spec=Document)
96+
document.get_word_before_cursor.return_value = ""
97+
document.text = ""
98+
document.cursor_position = 0
99+
100+
mock_cmd_app.completion_matches = ["foo", "bar"]
101+
mock_cmd_app.display_matches = ["Foo Display"] # Length mismatch
102+
103+
completions = list(completer.get_completions(document, None))
104+
105+
# Should ignore display_matches and use completion_matches for display
106+
assert len(completions) == 2
107+
assert completions[0].display == [('', 'foo')]
108+
assert completions[1].display == [('', 'bar')]
109+
110+
def test_get_completions_empty(self, mock_cmd_app):
111+
"""Test completion with no matches."""
112+
completer = pt_utils.Cmd2Completer(cast(any, mock_cmd_app))
113+
114+
document = Mock(spec=Document)
115+
document.get_word_before_cursor.return_value = ""
116+
document.text = ""
117+
document.cursor_position = 0
118+
119+
mock_cmd_app.completion_matches = []
120+
121+
completions = list(completer.get_completions(document, None))
122+
123+
assert len(completions) == 0
124+
125+
def test_init_with_custom_settings(self, mock_cmd_app):
126+
"""Test initializing with custom settings."""
127+
mock_parser = Mock()
128+
custom_settings = utils.CustomCompletionSettings(parser=mock_parser)
129+
completer = pt_utils.Cmd2Completer(cast(any, mock_cmd_app), custom_settings=custom_settings)
130+
131+
document = Mock(spec=Document)
132+
document.get_word_before_cursor.return_value = ""
133+
document.text = ""
134+
document.cursor_position = 0
135+
136+
mock_cmd_app.completion_matches = []
137+
138+
list(completer.get_completions(document, None))
139+
140+
mock_cmd_app.complete.assert_called_once()
141+
assert mock_cmd_app.complete.call_args[1]['custom_settings'] == custom_settings
142+
143+
144+
class TestCmd2History:
145+
def make_history_item(self, text):
146+
statement = Mock(spec=Statement)
147+
statement.raw = text
148+
item = Mock(spec=HistoryItem)
149+
item.statement = statement
150+
return item
151+
152+
def test_load_history_strings(self, mock_cmd_app):
153+
"""Test loading history strings in reverse order with deduping."""
154+
history = pt_utils.Cmd2History(cast(any, mock_cmd_app))
155+
156+
# Setup history items
157+
# History in cmd2 is oldest to newest
158+
items = [
159+
self.make_history_item("cmd1"),
160+
self.make_history_item("cmd2"),
161+
self.make_history_item("cmd2"), # Duplicate
162+
self.make_history_item("cmd3"),
163+
]
164+
mock_cmd_app.history = items
165+
166+
# Expected: cmd3, cmd2, cmd1 (duplicates removed)
167+
result = list(history.load_history_strings())
168+
169+
assert result == ["cmd3", "cmd2", "cmd1"]
170+
171+
def test_load_history_strings_empty(self, mock_cmd_app):
172+
"""Test loading history strings with empty history."""
173+
history = pt_utils.Cmd2History(cast(any, mock_cmd_app))
174+
175+
mock_cmd_app.history = []
176+
177+
result = list(history.load_history_strings())
178+
179+
assert result == []
180+
181+
def test_get_strings(self, mock_cmd_app):
182+
"""Test get_strings uses lazy loading."""
183+
history = pt_utils.Cmd2History(cast(any, mock_cmd_app))
184+
185+
items = [self.make_history_item("test")]
186+
mock_cmd_app.history = items
187+
188+
# Initially not loaded
189+
assert not history._loaded
190+
191+
strings = history.get_strings()
192+
193+
assert strings == ["test"]
194+
assert history._loaded
195+
assert history._loaded_strings == ["test"]
196+
197+
# Call again, should return cached strings
198+
# Modify underlying history to prove it uses cache
199+
mock_cmd_app.history = []
200+
strings2 = history.get_strings()
201+
assert strings2 == ["test"]
202+
203+
def test_store_string(self, mock_cmd_app):
204+
"""Test store_string does nothing."""
205+
history = pt_utils.Cmd2History(cast(any, mock_cmd_app))
206+
207+
# Just ensure it doesn't raise error or modify cmd2 history
208+
history.store_string("new command")
209+
210+
assert len(mock_cmd_app.history) == 0

0 commit comments

Comments
 (0)