22
33import itertools
44import shutil
5+ from types import SimpleNamespace
56from typing import Any , cast
67
78import click
@@ -93,6 +94,24 @@ def test_get_output_margin_renders_prompt_once_and_counts_status_lines(monkeypat
9394 assert cli .prompt_lines == 2
9495
9596
97+ def test_get_output_margin_uses_prompt_session_render_counter (monkeypatch : pytest .MonkeyPatch ) -> None :
98+ cli = make_bare_mycli ()
99+ cli .prompt_lines = 0
100+ cli .prompt_session = cast (Any , SimpleNamespace (app = SimpleNamespace (render_counter = 9 )))
101+ cli .get_reserved_space = lambda : 1 # type: ignore[assignment]
102+ render_counters : list [int ] = []
103+
104+ def render_prompt_string (_cli : Any , _prompt_format : str , render_counter : int ) -> FormattedText :
105+ render_counters .append (render_counter )
106+ return FormattedText ([('' , 'prompt' )])
107+
108+ monkeypatch .setattr (output_module .repl_mode , 'render_prompt_string' , render_prompt_string )
109+ monkeypatch .setattr (output_module .special , 'is_timing_enabled' , lambda : False )
110+
111+ assert OutputMixin .get_output_margin (cli ) == 2
112+ assert render_counters == [9 ]
113+
114+
96115def test_output_writes_lines_sinks_and_status (monkeypatch : pytest .MonkeyPatch ) -> None :
97116 cli = make_bare_mycli ()
98117 cli .prompt_session = None
@@ -124,6 +143,84 @@ def test_output_writes_lines_sinks_and_status(monkeypatch: pytest.MonkeyPatch) -
124143 assert list (printed_status [0 ])[0 ][0 ].strip () == 'class:output.status'
125144
126145
146+ def test_output_uses_prompt_session_size (monkeypatch : pytest .MonkeyPatch ) -> None :
147+ cli = make_bare_mycli ()
148+ cli .prompt_session = cast (
149+ Any ,
150+ SimpleNamespace (output = SimpleNamespace (get_size = lambda : SimpleNamespace (columns = 80 , rows = 24 ))),
151+ )
152+ cli .explicit_pager = False
153+ cli .log_output = lambda value : None # type: ignore[assignment]
154+ cli .get_output_margin = lambda status = None : 1 # type: ignore[assignment]
155+ printed_lines : list [str ] = []
156+ monkeypatch .setattr (output_module .special , 'write_tee' , lambda value : None )
157+ monkeypatch .setattr (output_module .special , 'write_once' , lambda value : None )
158+ monkeypatch .setattr (output_module .special , 'write_pipe_once' , lambda value : None )
159+ monkeypatch .setattr (output_module .special , 'is_redirected' , lambda : False )
160+ monkeypatch .setattr (output_module .special , 'is_pager_enabled' , lambda : False )
161+ monkeypatch .setattr (click , 'secho' , lambda value , ** _kwargs : printed_lines .append (value ))
162+
163+ OutputMixin .output (cli , itertools .chain (['row' ]), SQLResult ())
164+
165+ assert printed_lines == ['row' ]
166+
167+
168+ def test_output_flushes_buffer_when_content_does_not_fit (monkeypatch : pytest .MonkeyPatch ) -> None :
169+ cli = make_bare_mycli ()
170+ cli .prompt_session = None
171+ cli .explicit_pager = False
172+ cli .log_output = lambda value : None # type: ignore[assignment]
173+ cli .get_output_margin = lambda status = None : output_module .DEFAULT_HEIGHT # type: ignore[assignment]
174+ printed_lines : list [str ] = []
175+ monkeypatch .setattr (output_module .special , 'write_tee' , lambda value : None )
176+ monkeypatch .setattr (output_module .special , 'write_once' , lambda value : None )
177+ monkeypatch .setattr (output_module .special , 'write_pipe_once' , lambda value : None )
178+ monkeypatch .setattr (output_module .special , 'is_redirected' , lambda : False )
179+ monkeypatch .setattr (output_module .special , 'is_pager_enabled' , lambda : False )
180+ monkeypatch .setattr (click , 'secho' , lambda value , ** _kwargs : printed_lines .append (value ))
181+
182+ OutputMixin .output (cli , itertools .chain (['row 1' , 'row 2' ]), SQLResult ())
183+
184+ assert printed_lines == ['row 1' , 'row 2' ]
185+
186+
187+ def test_output_switches_to_pager_when_content_does_not_fit (monkeypatch : pytest .MonkeyPatch ) -> None :
188+ cli = make_bare_mycli ()
189+ cli .prompt_session = None
190+ cli .explicit_pager = False
191+ cli .log_output = lambda value : None # type: ignore[assignment]
192+ cli .get_output_margin = lambda status = None : output_module .DEFAULT_HEIGHT # type: ignore[assignment]
193+ paged_lines : list [str ] = []
194+ monkeypatch .setattr (output_module .special , 'write_tee' , lambda value : None )
195+ monkeypatch .setattr (output_module .special , 'write_once' , lambda value : None )
196+ monkeypatch .setattr (output_module .special , 'write_pipe_once' , lambda value : None )
197+ monkeypatch .setattr (output_module .special , 'is_redirected' , lambda : False )
198+ monkeypatch .setattr (output_module .special , 'is_pager_enabled' , lambda : True )
199+ monkeypatch .setattr (click , 'echo_via_pager' , lambda values : paged_lines .extend (list (values )))
200+
201+ OutputMixin .output (cli , itertools .chain (['row' ]), SQLResult ())
202+
203+ assert paged_lines == ['row\n ' ]
204+
205+
206+ def test_output_redirected_skips_screen_printing (monkeypatch : pytest .MonkeyPatch ) -> None :
207+ cli = make_bare_mycli ()
208+ cli .prompt_session = None
209+ cli .log_output = lambda value : None # type: ignore[assignment]
210+ cli .get_output_margin = lambda status = None : 1 # type: ignore[assignment]
211+ printed_lines : list [str ] = []
212+ monkeypatch .setattr (output_module .special , 'write_tee' , lambda value : None )
213+ monkeypatch .setattr (output_module .special , 'write_once' , lambda value : None )
214+ monkeypatch .setattr (output_module .special , 'write_pipe_once' , lambda value : None )
215+ monkeypatch .setattr (output_module .special , 'is_redirected' , lambda : True )
216+ monkeypatch .setattr (output_module .special , 'is_pager_enabled' , lambda : False )
217+ monkeypatch .setattr (click , 'secho' , lambda value , ** _kwargs : printed_lines .append (value ))
218+
219+ OutputMixin .output (cli , itertools .chain (['row' ]), SQLResult ())
220+
221+ assert printed_lines == []
222+
223+
127224def test_output_uses_warning_status_style (monkeypatch : pytest .MonkeyPatch ) -> None :
128225 cli = make_bare_mycli ()
129226 cli .log_output = lambda value : None # type: ignore[assignment]
@@ -136,6 +233,19 @@ def test_output_uses_warning_status_style(monkeypatch: pytest.MonkeyPatch) -> No
136233 assert list (printed_status [0 ])[0 ][0 ].strip () == 'class:warnings.status'
137234
138235
236+ def test_output_preserves_formatted_status (monkeypatch : pytest .MonkeyPatch ) -> None :
237+ cli = make_bare_mycli ()
238+ cli .log_output = lambda value : None # type: ignore[assignment]
239+ cli .get_output_margin = lambda status = None : 1 # type: ignore[assignment]
240+ printed_status : list [Any ] = []
241+ monkeypatch .setattr (prompt_toolkit , 'print_formatted_text' , lambda text , style = None : printed_status .append (text ))
242+ status = FormattedText ([('class:custom' , 'formatted' )])
243+
244+ OutputMixin .output (cli , itertools .chain ([]), SQLResult (status = status ))
245+
246+ assert to_plain_text (printed_status [0 ]) == 'formatted'
247+
248+
139249def test_output_sends_buffer_to_pager_when_pager_is_explicit (monkeypatch : pytest .MonkeyPatch ) -> None :
140250 cli = make_bare_mycli ()
141251 cli .prompt_session = None
@@ -156,6 +266,22 @@ def test_output_sends_buffer_to_pager_when_pager_is_explicit(monkeypatch: pytest
156266 assert paged_lines == ['row 1\n ' , 'row 2\n ' ]
157267
158268
269+ def test_configure_pager_uses_more_for_missing_less_on_windows (monkeypatch : pytest .MonkeyPatch ) -> None :
270+ cli = make_bare_mycli ()
271+ cli .my_cnf = ConfigObj ({'client' : {'pager' : 'less' }})
272+ cli .config = ConfigObj ({'main' : {'pager' : '' , 'enable_pager' : 'True' }})
273+ cli .read_my_cnf = lambda cnf , keys : {'pager' : 'less' , 'skip-pager' : None } # type: ignore[assignment]
274+ pager_calls : list [str ] = []
275+ monkeypatch .setattr (output_module , 'WIN' , True )
276+ monkeypatch .setattr (output_module .shutil , 'which' , lambda value : None )
277+ monkeypatch .setattr (output_module .special , 'set_pager' , lambda value : pager_calls .append (value ))
278+ monkeypatch .setattr (output_module .special , 'disable_pager' , lambda : None )
279+
280+ OutputMixin .configure_pager (cli )
281+
282+ assert pager_calls == ['more' ]
283+
284+
159285def test_configure_pager_prefers_my_cnf_pager_and_sets_less (monkeypatch : pytest .MonkeyPatch ) -> None :
160286 cli = make_bare_mycli ()
161287 cli .my_cnf = ConfigObj ({'client' : {'pager' : 'my-pager' }})
@@ -203,6 +329,17 @@ def test_format_sqlresult_uses_redirect_formatter_and_appends_preamble_postamble
203329 assert cli .redirect_formatter .calls
204330
205331
332+ def test_format_sqlresult_uses_null_string_when_default_missing_value_is_configured () -> None :
333+ cli = make_bare_mycli ()
334+ cli .main_formatter = DummyFormatter ()
335+ result = SQLResult (header = ['id' ], rows = [(None ,)])
336+
337+ list (OutputMixin .format_sqlresult (cli , result , null_string = '<null>' ))
338+
339+ _ , kwargs = cli .main_formatter .calls [- 1 ]
340+ assert kwargs ['missing_value' ] == '<null>'
341+
342+
206343def test_format_sqlresult_for_cursor_sets_column_types_and_alignment (monkeypatch : pytest .MonkeyPatch ) -> None :
207344 cli = make_bare_mycli ()
208345 cli .main_formatter = DummyFormatter ()
@@ -217,6 +354,42 @@ def test_format_sqlresult_for_cursor_sets_column_types_and_alignment(monkeypatch
217354 assert kwargs ['colalign' ] == ['left' , 'left' ]
218355
219356
357+ def test_format_sqlresult_for_empty_cursor_uses_empty_column_metadata (monkeypatch : pytest .MonkeyPatch ) -> None :
358+ cli = make_bare_mycli ()
359+ cli .main_formatter = DummyFormatter ()
360+ monkeypatch .setattr (output_module , 'Cursor' , FakeCursorBase )
361+ rows = FakeCursorBase (rowcount = 0 , description = [('id' , 3 )])
362+ result = SQLResult (header = ['id' ], rows = cast (Any , rows ))
363+
364+ list (OutputMixin .format_sqlresult (cli , result ))
365+
366+ _ , kwargs = cli .main_formatter .calls [- 1 ]
367+ assert kwargs ['column_types' ] == []
368+ assert kwargs ['colalign' ] == []
369+
370+
371+ def test_format_sqlresult_materializes_cursor_rows_when_width_is_limited (monkeypatch : pytest .MonkeyPatch ) -> None :
372+ cli = make_bare_mycli ()
373+ cli .main_formatter = DummyFormatter ()
374+ monkeypatch .setattr (output_module , 'Cursor' , FakeCursorBase )
375+ rows = FakeCursorBase (rows = [(1 ,)], rowcount = 1 , description = [('id' , 3 )])
376+ result = SQLResult (header = ['id' ], rows = cast (Any , rows ))
377+
378+ list (OutputMixin .format_sqlresult (cli , result , max_width = 100 ))
379+
380+ formatted_rows = cli .main_formatter .calls [- 1 ][0 ][0 ]
381+ assert formatted_rows == [(1 ,)]
382+
383+
384+ def test_format_sqlresult_splits_string_formatter_output () -> None :
385+ cli = make_bare_mycli ()
386+ cli .main_formatter = DummyFormatter ()
387+ cli .main_formatter .format_output = lambda * args , ** kwargs : 'one\n two' # type: ignore[method-assign]
388+ result = SQLResult (header = ['id' ], rows = [(1 ,)])
389+
390+ assert list (OutputMixin .format_sqlresult (cli , result )) == ['one' , 'two' ]
391+
392+
220393def test_format_sqlresult_switches_to_vertical_when_first_line_is_too_wide () -> None :
221394 cli = make_bare_mycli ()
222395 cli .main_formatter = DummyFormatter ()
@@ -225,6 +398,29 @@ def test_format_sqlresult_switches_to_vertical_when_first_line_is_too_wide() ->
225398 assert list (OutputMixin .format_sqlresult (cli , result , max_width = 2 )) == ['vertical output' ]
226399
227400
401+ def test_format_sqlresult_splits_string_vertical_output_when_table_is_too_wide () -> None :
402+ cli = make_bare_mycli ()
403+ cli .main_formatter = DummyFormatter ()
404+
405+ def format_output (rows , header , format_name = None , ** kwargs ):
406+ if format_name == 'vertical' :
407+ return 'vertical one\n vertical two'
408+ return ['too wide' ]
409+
410+ cli .main_formatter .format_output = format_output # type: ignore[method-assign]
411+ result = SQLResult (header = ['id' ], rows = [(1 ,)])
412+
413+ assert list (OutputMixin .format_sqlresult (cli , result , max_width = 2 )) == ['vertical one' , 'vertical two' ]
414+
415+
416+ def test_format_sqlresult_keeps_table_when_first_line_fits_width () -> None :
417+ cli = make_bare_mycli ()
418+ cli .main_formatter = DummyFormatter ()
419+ result = SQLResult (header = ['id' ], rows = [(1 ,)])
420+
421+ assert list (OutputMixin .format_sqlresult (cli , result , max_width = 100 )) == ['plain output' ]
422+
423+
228424def test_get_reserved_space_caps_ratio (monkeypatch : pytest .MonkeyPatch ) -> None :
229425 cli = make_bare_mycli ()
230426 monkeypatch .setattr (shutil , 'get_terminal_size' , lambda * args , ** kwargs : (120 , 40 ))
0 commit comments