Skip to content

Commit ab187d7

Browse files
committed
feat(tests): create test for the multiline inputs feature
1 parent 039303b commit ab187d7

File tree

1 file changed

+297
-0
lines changed

1 file changed

+297
-0
lines changed
Lines changed: 297 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,297 @@
1+
"""Tests for multiline input functionality in commit command."""
2+
3+
from unittest.mock import Mock, patch
4+
5+
import pytest
6+
from prompt_toolkit.key_binding.key_processor import KeyPressEvent
7+
from prompt_toolkit.keys import Keys
8+
9+
from commitizen.commands.commit import Commit
10+
from commitizen.config import BaseConfig
11+
12+
13+
class TestCommitMultilineFeature:
14+
"""Test suite for multiline input functionality."""
15+
16+
@pytest.fixture
17+
def config(self):
18+
"""Create a test configuration."""
19+
_config = BaseConfig()
20+
_config.settings.update({"name": "cz_conventional_commits"})
21+
return _config
22+
23+
@pytest.fixture
24+
def commit_cmd(self, config):
25+
"""Create a Commit command instance."""
26+
return Commit(config, {})
27+
28+
def test_optional_field_detection(self, commit_cmd):
29+
"""Test that optional fields are correctly identified."""
30+
# Test field with default=""
31+
optional_question = {"default": "", "message": "Optional field"}
32+
result = (
33+
optional_question.get("default") == ""
34+
or "skip" in optional_question.get("message", "").lower()
35+
)
36+
assert result is True
37+
38+
# Test field with "skip" in message
39+
skip_question = {"message": "Press enter to skip this field"}
40+
result = (
41+
skip_question.get("default") == ""
42+
or "skip" in skip_question.get("message", "").lower()
43+
)
44+
assert result is True
45+
46+
# Test required field
47+
required_question = {"message": "Required field"}
48+
result = (
49+
required_question.get("default") == ""
50+
or "skip" in required_question.get("message", "").lower()
51+
)
52+
assert result is False
53+
54+
@patch("questionary.prompts.text.text")
55+
def test_optional_field_empty_input_skips(self, mock_text, commit_cmd):
56+
"""Test that pressing Enter on empty optional field skips it."""
57+
# Mock the text prompt to return empty string
58+
mock_text_instance = Mock()
59+
mock_text_instance.ask.return_value = ""
60+
mock_text.return_value = mock_text_instance
61+
62+
# Create an optional question
63+
question = {
64+
"type": "input",
65+
"name": "scope",
66+
"message": "What is the scope? (press [enter] to skip)",
67+
"multiline": True,
68+
"default": "",
69+
"filter": lambda x: x.strip(),
70+
}
71+
72+
# Test the multiline handling logic
73+
is_optional = (
74+
question.get("default") == ""
75+
or "skip" in question.get("message", "").lower()
76+
)
77+
assert is_optional is True
78+
79+
# Verify that empty input results in default value
80+
if is_optional:
81+
result = ""
82+
if not result.strip():
83+
result = question.get("default", "")
84+
assert result == ""
85+
86+
@patch("questionary.prompts.text.text")
87+
def test_optional_field_multiline_content(self, mock_text, commit_cmd):
88+
"""Test that optional fields can handle multiline content."""
89+
# Mock the text prompt to return multiline content
90+
multiline_content = "Line 1\nLine 2\nLine 3"
91+
mock_text_instance = Mock()
92+
mock_text_instance.ask.return_value = multiline_content
93+
mock_text.return_value = mock_text_instance
94+
95+
# Verify multiline content is preserved
96+
result = multiline_content
97+
assert "\n" in result
98+
assert result.count("\n") == 2 # Two newlines for three lines
99+
100+
def test_required_field_detection(self, commit_cmd):
101+
"""Test that required fields are correctly identified."""
102+
required_question = {
103+
"type": "input",
104+
"name": "subject",
105+
"message": "Write a short summary",
106+
"multiline": True,
107+
}
108+
109+
is_optional = (
110+
required_question.get("default") == ""
111+
or "skip" in required_question.get("message", "").lower()
112+
)
113+
assert is_optional is False
114+
115+
@patch("builtins.print")
116+
def test_required_field_empty_input_shows_error(self, mock_print, commit_cmd):
117+
"""Test that empty required field shows error message."""
118+
# Simulate the error handling for required fields
119+
buffer_text = "" # Empty input
120+
121+
if not buffer_text.strip():
122+
# This is what our key binding does for required fields
123+
error_msg = "\n\033[91m⚠ This field is required. Please enter some content or press Ctrl+C to abort.\033[0m"
124+
prompt_msg = "> "
125+
126+
# Verify the error message would be shown
127+
assert "required" in error_msg
128+
assert "Ctrl+C" in error_msg
129+
assert prompt_msg == "> "
130+
131+
@patch("questionary.prompts.text.text")
132+
def test_required_field_with_content_succeeds(self, mock_text, commit_cmd):
133+
"""Test that required fields with content work properly."""
134+
# Mock the text prompt to return valid content
135+
valid_content = "Add user authentication feature"
136+
mock_text_instance = Mock()
137+
mock_text_instance.ask.return_value = valid_content
138+
mock_text.return_value = mock_text_instance
139+
140+
# Create a required question
141+
question = {
142+
"type": "input",
143+
"name": "subject",
144+
"message": "Write a short summary",
145+
"multiline": True,
146+
"filter": lambda x: x.strip(".").strip(),
147+
}
148+
149+
# Test that content is processed correctly
150+
result = valid_content
151+
if "filter" in question:
152+
result = question["filter"](result)
153+
154+
assert result == "Add user authentication feature"
155+
assert len(result) > 0
156+
157+
def test_key_binding_setup_for_optional_fields(self):
158+
"""Test that key bindings are set up correctly for optional fields."""
159+
from prompt_toolkit.key_binding import KeyBindings
160+
161+
bindings = KeyBindings()
162+
163+
# Mock the key binding function for optional fields
164+
def mock_optional_handler(event: KeyPressEvent) -> None:
165+
buffer = event.current_buffer
166+
if not buffer.text.strip():
167+
# Should exit with empty result for optional fields
168+
event.app.exit(result=buffer.text)
169+
else:
170+
# Should add newline for content
171+
buffer.newline()
172+
173+
# Add the binding
174+
bindings.add(Keys.Enter)(mock_optional_handler)
175+
176+
# Verify binding was added
177+
assert len(bindings.bindings) > 0
178+
179+
def test_key_binding_setup_for_required_fields(self):
180+
"""Test that key bindings are set up correctly for required fields."""
181+
from prompt_toolkit.key_binding import KeyBindings
182+
183+
bindings = KeyBindings()
184+
185+
# Mock the key binding function for required fields
186+
def mock_required_handler(event: KeyPressEvent) -> None:
187+
buffer = event.current_buffer
188+
if not buffer.text.strip():
189+
# Should show error and do nothing for required fields
190+
print("Error: Field is required")
191+
pass
192+
else:
193+
# Should add newline for content
194+
buffer.newline()
195+
196+
# Add the binding
197+
bindings.add(Keys.Enter)(mock_required_handler)
198+
199+
# Verify binding was added
200+
assert len(bindings.bindings) > 0
201+
202+
@patch("questionary.prompts.text.text")
203+
@patch("questionary.prompt")
204+
def test_fallback_to_standard_prompt(self, mock_prompt, mock_text, commit_cmd):
205+
"""Test that fallback works when custom prompt fails."""
206+
# Mock the text prompt to raise an exception
207+
mock_text.side_effect = Exception("Custom prompt failed")
208+
209+
# Mock the standard prompt
210+
mock_prompt.return_value = {"test_field": "fallback value"}
211+
212+
# This would be handled in the except block of our implementation
213+
try:
214+
raise Exception("Custom prompt failed")
215+
except Exception:
216+
# Fallback to standard questionary prompt
217+
result = {"test_field": "fallback value"}
218+
assert result["test_field"] == "fallback value"
219+
220+
def test_multiline_question_configuration(self):
221+
"""Test that multiline questions are configured properly."""
222+
# Test configuration for optional field
223+
optional_question = {
224+
"type": "input",
225+
"name": "scope",
226+
"message": "Scope (press [enter] to skip)",
227+
"multiline": True,
228+
"default": "",
229+
}
230+
231+
assert optional_question["multiline"] is True
232+
assert optional_question.get("default") == ""
233+
assert "type" in optional_question
234+
assert "name" in optional_question
235+
assert "message" in optional_question
236+
237+
# Test configuration for required field
238+
required_question = {
239+
"type": "input",
240+
"name": "subject",
241+
"message": "Summary",
242+
"multiline": True,
243+
}
244+
245+
assert required_question["multiline"] is True
246+
assert (
247+
"default" not in required_question or required_question.get("default") != ""
248+
)
249+
250+
@patch("builtins.print")
251+
def test_user_guidance_messages(self, mock_print):
252+
"""Test that proper guidance messages are shown."""
253+
# Test optional field guidance
254+
is_optional = True
255+
if is_optional:
256+
expected_msg = "\033[90m💡 Multiline input:\n Press Enter on empty line to skip, Enter after text for new lines, Alt+Enter to finish\033[0m"
257+
# Verify the message format
258+
assert "Enter on empty line to skip" in expected_msg
259+
assert "Alt+Enter to finish" in expected_msg
260+
261+
# Test required field guidance
262+
is_optional = False
263+
if not is_optional:
264+
expected_msg = "\033[90m💡 Multiline input:\n Press Enter for new lines and Alt+Enter to finish\033[0m"
265+
# Verify the message format
266+
assert "Enter for new lines" in expected_msg
267+
assert "Alt+Enter to finish" in expected_msg
268+
269+
def test_filter_application(self):
270+
"""Test that filters are applied correctly to multiline input."""
271+
272+
# Test scope filter (removes spaces, joins with dashes)
273+
def _parse_scope(text: str) -> str:
274+
return "-".join(text.strip().split())
275+
276+
scope_input = "user auth module"
277+
filtered_scope = _parse_scope(scope_input)
278+
assert filtered_scope == "user-auth-module"
279+
280+
# Test simple subject processing (strip whitespace then dots)
281+
def simple_subject_filter(text: str) -> str:
282+
return text.strip().rstrip(".")
283+
284+
subject_input = "Add new feature. "
285+
filtered_subject = simple_subject_filter(subject_input)
286+
assert filtered_subject == "Add new feature"
287+
288+
def test_answer_dictionary_structure(self):
289+
"""Test that answers are structured correctly."""
290+
field_name = "test_field"
291+
result = "test content"
292+
293+
answer = {field_name: result}
294+
295+
assert isinstance(answer, dict)
296+
assert field_name in answer
297+
assert answer[field_name] == result

0 commit comments

Comments
 (0)