99 from collections .abc import Mapping
1010
1111 import pytest
12+ from pytest_mock import MockerFixture
1213
1314 from commitizen .question import CzQuestion
1415
@@ -38,6 +39,13 @@ def info(self) -> str:
3839 return ""
3940
4041
42+ def _make_fake_prompt_app (mocker : MockerFixture , buffer_text : str ):
43+ """Object graph for get_app().layout.current_buffer.text (prompt_toolkit)."""
44+ app = mocker .Mock ()
45+ app .layout .current_buffer .text = buffer_text
46+ return app
47+
48+
4149def test_build_preview_questions_disabled_returns_original_list (config ):
4250 cz = PreviewCz (config )
4351 questions : list [CzQuestion ] = [
@@ -50,22 +58,14 @@ def test_build_preview_questions_disabled_returns_original_list(config):
5058
5159def test_build_preview_questions_wraps_filter_and_updates_answers_state (
5260 monkeypatch : pytest .MonkeyPatch ,
61+ mocker : MockerFixture ,
5362 config ,
5463):
5564 cz = PreviewCz (config )
5665
5766 def original_filter (raw : str ) -> str :
5867 return raw .strip ().upper ()
5968
60- class DummyBuffer :
61- text = " hello "
62-
63- class DummyLayout :
64- current_buffer = DummyBuffer ()
65-
66- class DummyApp :
67- layout = DummyLayout ()
68-
6969 questions : list [CzQuestion ] = [
7070 {
7171 "type" : "input" ,
@@ -78,13 +78,12 @@ class DummyApp:
7878 enhanced = build_preview_questions (cz , questions , enabled = True , max_length = 50 )
7979 q = enhanced [0 ]
8080 assert q ["filter" ] is not original_filter
81-
82- # First update state via filter wrapper
8381 assert q ["filter" ](" hello " ) == "HELLO"
8482
85- # Then call toolbar which uses subject_builder -> cz.message() using answers_state
86- # and the current buffer text for the active field.
87- monkeypatch .setattr ("commitizen.preview_questions.get_app" , lambda : DummyApp ())
83+ monkeypatch .setattr (
84+ "commitizen.preview_questions.get_app" ,
85+ lambda : _make_fake_prompt_app (mocker , " hello " ),
86+ )
8887 toolbar_text = q ["bottom_toolbar" ]()
8988 assert "HELLO" in toolbar_text
9089 assert cz .calls , "cz.message should be called by toolbar rendering"
@@ -121,28 +120,22 @@ def test_build_preview_questions_adds_validate_only_for_supported_types(config):
121120
122121def test_toolbar_uses_current_buffer_text_and_subject_builder (
123122 monkeypatch : pytest .MonkeyPatch ,
123+ mocker : MockerFixture ,
124124 config ,
125125):
126126 cz = PreviewCz (config )
127127
128- class DummyBuffer :
129- text = "buffered"
130-
131- class DummyLayout :
132- current_buffer = DummyBuffer ()
133-
134- class DummyApp :
135- layout = DummyLayout ()
136-
137- monkeypatch .setattr ("commitizen.preview_questions.get_app" , lambda : DummyApp ())
128+ monkeypatch .setattr (
129+ "commitizen.preview_questions.get_app" ,
130+ lambda : _make_fake_prompt_app (mocker , "buffered" ),
131+ )
138132
139133 questions : list [CzQuestion ] = [
140134 {"type" : "input" , "name" : "subject" , "message" : "Subject" },
141135 ]
142136 enhanced = build_preview_questions (cz , questions , enabled = True , max_length = 50 )
143137 toolbar_text = enhanced [0 ]["bottom_toolbar" ]()
144138
145- # DummyCz.message uses subject from current buffer text via subject_builder
146139 assert "buffered" in toolbar_text
147140
148141
@@ -165,6 +158,7 @@ def test_get_current_buffer_text_on_get_app_exception_returns_empty(
165158
166159def test_subject_builder_applies_field_filter_and_handles_filter_exception (
167160 monkeypatch : pytest .MonkeyPatch ,
161+ mocker : MockerFixture ,
168162 config ,
169163):
170164 cz = PreviewCz (config )
@@ -175,15 +169,6 @@ def ok_filter(raw: str) -> str:
175169 def boom_filter (_raw : str ) -> str :
176170 raise RuntimeError ("boom" )
177171
178- class DummyBuffer :
179- text = " SCOPE "
180-
181- class DummyLayout :
182- current_buffer = DummyBuffer ()
183-
184- class DummyApp :
185- layout = DummyLayout ()
186-
187172 questions : list [CzQuestion ] = [
188173 {"type" : "input" , "name" : "subject" , "message" : "Subject" , "filter" : ok_filter },
189174 {"type" : "input" , "name" : "scope" , "message" : "Scope" , "filter" : boom_filter },
@@ -194,7 +179,10 @@ class DummyApp:
194179 enhanced [0 ]["filter" ](" hi " )
195180 # When rendering toolbar for current field 'scope', subject_builder will apply the
196181 # field filter to the current buffer text; filter exceptions must fallback to raw.
197- monkeypatch .setattr ("commitizen.preview_questions.get_app" , lambda : DummyApp ())
182+ monkeypatch .setattr (
183+ "commitizen.preview_questions.get_app" ,
184+ lambda : _make_fake_prompt_app (mocker , " SCOPE " ),
185+ )
198186
199187 # Render toolbar for scope and ensure it still includes subject, and scope raw is used
200188 toolbar_text = enhanced [1 ]["bottom_toolbar" ]()
@@ -205,7 +193,7 @@ class DummyApp:
205193def test_subject_builder_handles_cz_message_exception_returns_empty (
206194 monkeypatch : pytest .MonkeyPatch ,
207195 config ,
208- mocker ,
196+ mocker : MockerFixture ,
209197):
210198 class BoomCz (PreviewCz ):
211199 def message (self , _answers : Mapping [str , Any ]) -> str :
@@ -218,7 +206,6 @@ def message(self, _answers: Mapping[str, Any]) -> str:
218206 ]
219207 enhanced = build_preview_questions (cz , questions , enabled = True , max_length = 50 )
220208
221- # Force deterministic terminal width to avoid wrap dependence
222209 monkeypatch .setattr (
223210 "commitizen.interactive_preview.get_terminal_size" ,
224211 lambda : mocker .Mock (columns = 80 ),
0 commit comments