Skip to content

Commit 6f16e67

Browse files
committed
Adding unit tests for text alignment functions
1 parent cda57dc commit 6f16e67

File tree

2 files changed

+165
-31
lines changed

2 files changed

+165
-31
lines changed

cmd2/utils.py

Lines changed: 13 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -642,7 +642,7 @@ def align_text(text: str, *, fill_char: str = ' ', width: Optional[int] = None,
642642
"""
643643
Align text for display within a given width. Supports characters with display widths greater than 1.
644644
ANSI escape sequences are safely ignored and do not count toward the display width. This means colored text is
645-
supported. Each line in text will be aligned independently.
645+
supported. If text has line breaks, then each line is aligned independently.
646646
647647
There are convenience wrappers around this function: ljustify_text(), center_text(), and rjustify_text()
648648
@@ -653,22 +653,21 @@ def align_text(text: str, *, fill_char: str = ' ', width: Optional[int] = None,
653653
be converted to a space.
654654
:param alignment: how to align the text
655655
:return: aligned text
656-
:raises: ValueError if text or fill_char contains an unprintable character
657-
TypeError if fill_char is more than one character
658-
656+
:raises: TypeError if fill_char is more than one character
657+
ValueError if text or fill_char contains an unprintable character
659658
"""
660659
import io
661660
import shutil
662661

663662
from . import ansi
664663

665664
# Handle tabs
666-
text.replace('\t', ' ' * tab_width)
665+
text = text.replace('\t', ' ' * tab_width)
667666
if fill_char == '\t':
668667
fill_char = ' '
669668

670669
if len(fill_char) != 1:
671-
raise ValueError("Fill character must be exactly one character long")
670+
raise TypeError("Fill character must be exactly one character long")
672671

673672
fill_char_width = ansi.ansi_safe_wcswidth(fill_char)
674673
if fill_char_width == -1:
@@ -679,22 +678,21 @@ def align_text(text: str, *, fill_char: str = ' ', width: Optional[int] = None,
679678
else:
680679
lines = ['']
681680

681+
if width is None:
682+
width = shutil.get_terminal_size().columns
683+
682684
text_buf = io.StringIO()
683685

684686
for index, line in enumerate(lines):
685687
if index > 0:
686688
text_buf.write('\n')
687689

688-
# Use ansi_safe_wcswidth to support characters with display widths greater than 1
689-
# as well as ANSI escape sequences
690+
# Use ansi_safe_wcswidth to support characters with display widths
691+
# greater than 1 as well as ANSI escape sequences
690692
line_width = ansi.ansi_safe_wcswidth(line)
691693
if line_width == -1:
692-
# This can happen if text contains characters like newlines or tabs
693694
raise(ValueError("Text to align contains an unprintable character"))
694695

695-
if width is None:
696-
width = shutil.get_terminal_size().columns
697-
698696
# Check if line is wider than the desired final width
699697
if width <= line_width:
700698
text_buf.write(line)
@@ -731,7 +729,7 @@ def ljustify_text(text: str, *, fill_char: str = ' ', width: Optional[int] = Non
731729
"""
732730
Left justify text for display within a given width. Supports characters with display widths greater than 1.
733731
ANSI escape sequences are safely ignored and do not count toward the display width. This means colored text is
734-
supported. Each line in text will be aligned independently.
732+
supported. If text has line breaks, then each line is aligned independently.
735733
736734
:param text: text to left justify (Can contain multiple lines)
737735
:param fill_char: character that fills the alignment gap. Defaults to space. (Cannot be a line breaking character)
@@ -748,7 +746,7 @@ def center_text(text: str, *, fill_char: str = ' ', width: Optional[int] = None,
748746
"""
749747
Center text for display within a given width. Supports characters with display widths greater than 1.
750748
ANSI escape sequences are safely ignored and do not count toward the display width. This means colored text is
751-
supported. Each line in text will be aligned independently.
749+
supported. If text has line breaks, then each line is aligned independently.
752750
753751
:param text: text to center (Can contain multiple lines)
754752
:param fill_char: character that fills the alignment gap. Defaults to space. (Cannot be a line breaking character)
@@ -765,7 +763,7 @@ def rjustify_text(text: str, *, fill_char: str = ' ', width: Optional[int] = Non
765763
"""
766764
Right justify text for display within a given width. Supports characters with display widths greater than 1.
767765
ANSI escape sequences are safely ignored and do not count toward the display width. This means colored text is
768-
supported. Each line in text will be aligned independently.
766+
supported. If text has line breaks, then each line is aligned independently.
769767
770768
:param text: text to right justify (Can contain multiple lines)
771769
:param fill_char: character that fills the alignment gap. Defaults to space. (Cannot be a line breaking character)

tests/test_utils.py

Lines changed: 152 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -293,21 +293,157 @@ def test_context_flag_exit_err(context_flag):
293293
context_flag.__exit__()
294294

295295

296-
def test_center_text_pad_equals():
297-
msg = 'foo'
298-
fill_char = '='
299-
centered = cu.center_text(msg, fill_char=fill_char)
300-
assert msg in centered
301-
assert centered.startswith(fill_char)
302-
assert centered.endswith(fill_char)
303-
letters_in_centered = set(centered)
304-
letters_in_msg = set(msg)
305-
assert len(letters_in_centered) == len(letters_in_msg) + 1
306-
307-
308-
def test_center_text_pad_blank():
309-
msg = 'foo'
310-
fill_char = ''
296+
def test_align_text_fill_char_is_tab():
297+
text = 'foo'
298+
fill_char = '\t'
299+
width = 5
300+
aligned = cu.align_text(text, fill_char=fill_char, width=width, alignment=cu.TextAlignment.LEFT)
301+
assert aligned == text + ' '
302+
303+
def test_align_text_fill_char_is_too_long():
304+
text = 'foo'
305+
fill_char = 'fill'
306+
width = 5
307+
with pytest.raises(TypeError):
308+
cu.align_text(text, fill_char=fill_char, width=width, alignment=cu.TextAlignment.LEFT)
311309

310+
def test_align_text_fill_char_is_unprintable():
311+
text = 'foo'
312+
fill_char = '\n'
313+
width = 5
312314
with pytest.raises(ValueError):
313-
cu.center_text(msg, fill_char=fill_char)
315+
cu.align_text(text, fill_char=fill_char, width=width, alignment=cu.TextAlignment.LEFT)
316+
317+
def test_align_text_has_tabs():
318+
text = '\t\tfoo'
319+
fill_char = '-'
320+
width = 10
321+
aligned = cu.align_text(text, fill_char=fill_char, width=width, alignment=cu.TextAlignment.LEFT, tab_width=2)
322+
assert aligned == ' ' + 'foo' + '---'
323+
324+
def test_align_text_blank():
325+
text = ''
326+
fill_char = '-'
327+
width = 5
328+
aligned = cu.align_text(text, fill_char=fill_char, width=width, alignment=cu.TextAlignment.LEFT)
329+
assert aligned == text + fill_char * width
330+
331+
def test_align_text_wider_than_width():
332+
text = 'long'
333+
fill_char = '-'
334+
width = 3
335+
aligned = cu.align_text(text, fill_char=fill_char, width=width, alignment=cu.TextAlignment.LEFT)
336+
assert aligned == text
337+
338+
def test_align_text_term_width():
339+
import shutil
340+
from cmd2 import ansi
341+
text = 'foo'
342+
fill_char = ' '
343+
344+
term_width = shutil.get_terminal_size().columns
345+
expected_fill = (term_width - ansi.ansi_safe_wcswidth(text)) * fill_char
346+
347+
aligned = cu.align_text(text, fill_char=fill_char, alignment=cu.TextAlignment.LEFT)
348+
assert aligned == text + expected_fill
349+
350+
def test_left_text():
351+
text = 'foo'
352+
fill_char = '-'
353+
width = 5
354+
aligned = cu.ljustify_text(text, fill_char=fill_char, width=width)
355+
assert aligned == text + fill_char + fill_char
356+
357+
def test_left_text_multiline():
358+
text = "foo\nshoes"
359+
fill_char = '-'
360+
width = 7
361+
aligned = cu.ljustify_text(text, fill_char=fill_char, width=width)
362+
assert aligned == ('foo----\n'
363+
'shoes--')
364+
365+
def test_left_text_asian_fill():
366+
"""Test fill_char with display width greater than 1"""
367+
text = 'foo'
368+
fill_char = '苹'
369+
width = 5
370+
aligned = cu.ljustify_text(text, fill_char=fill_char, width=width)
371+
assert aligned == text + fill_char
372+
373+
def test_left_text_asian_fill_needs_padding():
374+
"""Test when fill_char's display width does not divide evenly into gap"""
375+
text = 'foo'
376+
fill_char = '苹'
377+
width = 6
378+
aligned = cu.ljustify_text(text, fill_char=fill_char, width=width)
379+
assert aligned == text + fill_char + ' '
380+
381+
def test_center_text():
382+
text = 'foo'
383+
fill_char = '-'
384+
width = 5
385+
aligned = cu.center_text(text,fill_char=fill_char, width=width)
386+
assert aligned == fill_char + text + fill_char
387+
388+
def test_center_text_multiline():
389+
text = "foo\nshoes"
390+
fill_char = '-'
391+
width = 7
392+
aligned = cu.center_text(text, fill_char=fill_char, width=width)
393+
assert aligned == ('--foo--\n'
394+
'-shoes-')
395+
396+
def test_center_text_asian_fill():
397+
"""Test fill_char with display width greater than 1"""
398+
text = 'foo'
399+
fill_char = '苹'
400+
width = 7
401+
aligned = cu.center_text(text, fill_char=fill_char, width=width)
402+
assert aligned == fill_char + text + fill_char
403+
404+
def test_center_text_asian_fill_needs_right_padding():
405+
"""Test when fill_char's display width does not divide evenly into right gap"""
406+
text = 'foo'
407+
fill_char = '苹'
408+
width = 8
409+
aligned = cu.center_text(text, fill_char=fill_char, width=width)
410+
assert aligned == fill_char + text + fill_char + ' '
411+
412+
def test_center_text_asian_fill_needs_left_and_right_padding():
413+
"""Test when fill_char's display width does not divide evenly into either gap"""
414+
text = 'foo'
415+
fill_char = '苹'
416+
width = 9
417+
aligned = cu.center_text(text, fill_char=fill_char, width=width)
418+
assert aligned == fill_char + ' ' + text + fill_char + ' '
419+
420+
def test_right_text():
421+
text = 'foo'
422+
fill_char = '-'
423+
width = 5
424+
aligned = cu.rjustify_text(text, fill_char=fill_char, width=width)
425+
assert aligned == fill_char + fill_char + text
426+
427+
def test_right_text_multiline():
428+
text = "foo\nshoes"
429+
fill_char = '-'
430+
width = 7
431+
aligned = cu.rjustify_text(text, fill_char=fill_char, width=width)
432+
assert aligned == ('----foo\n'
433+
'--shoes')
434+
435+
def test_right_text_asian_fill():
436+
"""Test fill_char with display width greater than 1"""
437+
text = 'foo'
438+
fill_char = '苹'
439+
width = 5
440+
aligned = cu.rjustify_text(text, fill_char=fill_char, width=width)
441+
assert aligned == fill_char + text
442+
443+
def test_right_text_asian_fill_needs_padding():
444+
"""Test when fill_char's display width does not divide evenly into gap"""
445+
text = 'foo'
446+
fill_char = '苹'
447+
width = 6
448+
aligned = cu.rjustify_text(text, fill_char=fill_char, width=width)
449+
assert aligned == fill_char + ' ' + text

0 commit comments

Comments
 (0)