1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745
|
# coding=utf-8
# flake8: noqa E501
"""
Unit testing for cmd2/table_creator.py module
"""
import pytest
from cmd2 import (
Bg,
Fg,
TextStyle,
ansi,
)
from cmd2.table_creator import (
AlternatingTable,
BorderedTable,
Column,
HorizontalAlignment,
SimpleTable,
TableCreator,
VerticalAlignment,
)
# Turn off black formatting for entire file so multiline strings
# can be visually aligned to match the tables being tested.
# fmt: off
def test_column_creation():
# Width less than 1
with pytest.raises(ValueError) as excinfo:
Column("Column 1", width=0)
assert "Column width cannot be less than 1" in str(excinfo.value)
# Width specified
c = Column("header", width=20)
assert c.width == 20
# max_data_lines less than 1
with pytest.raises(ValueError) as excinfo:
Column("Column 1", max_data_lines=0)
assert "Max data lines cannot be less than 1" in str(excinfo.value)
# No width specified, blank label
c = Column("")
assert c.width < 0
tc = TableCreator([c])
assert tc.cols[0].width == 1
# No width specified, label isn't blank but has no width
c = Column(ansi.style('', fg=Fg.GREEN))
assert c.width < 0
tc = TableCreator([c])
assert tc.cols[0].width == 1
# No width specified, label has width
c = Column("a line")
assert c.width < 0
tc = TableCreator([c])
assert tc.cols[0].width == ansi.style_aware_wcswidth("a line")
# No width specified, label has width and multiple lines
c = Column("short\nreally long")
assert c.width < 0
tc = TableCreator([c])
assert tc.cols[0].width == ansi.style_aware_wcswidth("really long")
# No width specified, label has tabs
c = Column("line\twith\ttabs")
assert c.width < 0
tc = TableCreator([c])
assert tc.cols[0].width == ansi.style_aware_wcswidth("line with tabs")
# Add basic tests for style_header_text and style_data_text to make sure these members don't get removed.
c = Column("Column 1")
assert c.style_header_text is True
assert c.style_data_text is True
c = Column("Column 1", style_header_text=False)
assert c.style_header_text is False
assert c.style_data_text is True
c = Column("Column 1", style_data_text=False)
assert c.style_header_text is True
assert c.style_data_text is False
def test_column_alignment():
column_1 = Column(
"Col 1",
width=10,
header_horiz_align=HorizontalAlignment.LEFT,
header_vert_align=VerticalAlignment.TOP,
data_horiz_align=HorizontalAlignment.RIGHT,
data_vert_align=VerticalAlignment.BOTTOM,
)
column_2 = Column(
"Col 2",
width=10,
header_horiz_align=HorizontalAlignment.RIGHT,
header_vert_align=VerticalAlignment.BOTTOM,
data_horiz_align=HorizontalAlignment.CENTER,
data_vert_align=VerticalAlignment.MIDDLE,
)
column_3 = Column(
"Col 3",
width=10,
header_horiz_align=HorizontalAlignment.CENTER,
header_vert_align=VerticalAlignment.MIDDLE,
data_horiz_align=HorizontalAlignment.LEFT,
data_vert_align=VerticalAlignment.TOP,
)
column_4 = Column("Three\nline\nheader", width=10)
columns = [column_1, column_2, column_3, column_4]
tc = TableCreator(columns)
# Check defaults
assert column_4.header_horiz_align == HorizontalAlignment.LEFT
assert column_4.header_vert_align == VerticalAlignment.BOTTOM
assert column_4.data_horiz_align == HorizontalAlignment.LEFT
assert column_4.data_vert_align == VerticalAlignment.TOP
# Create a header row
row_data = [col.header for col in columns]
header = tc.generate_row(row_data=row_data, is_header=True)
assert header == (
'Col 1 Three \n'
' Col 3 line \n'
' Col 2 header '
)
# Create a data row
row_data = ["Val 1", "Val 2", "Val 3", "Three\nline\ndata"]
row = tc.generate_row(row_data=row_data, is_header=False)
assert row == (
' Val 3 Three \n'
' Val 2 line \n'
' Val 1 data '
)
def test_blank_last_line():
"""This tests that an empty line is inserted when the last data line is blank"""
column_1 = Column("Col 1", width=10)
tc = TableCreator([column_1])
row_data = ['my line\n\n']
row = tc.generate_row(row_data=row_data, is_header=False)
assert row == ('my line \n'
' ')
row_data = ['\n']
row = tc.generate_row(row_data=row_data, is_header=False)
assert row == ' '
row_data = ['']
row = tc.generate_row(row_data=row_data, is_header=False)
assert row == ' '
def test_wrap_text():
column_1 = Column("Col 1", width=10)
tc = TableCreator([column_1])
# Test normal wrapping
row_data = ['Some text to wrap\nA new line that will wrap\nNot wrap\n 1 2 3']
row = tc.generate_row(row_data=row_data, is_header=False)
assert row == ('Some text \n'
'to wrap \n'
'A new line\n'
'that will \n'
'wrap \n'
'Not wrap \n'
' 1 2 3 ')
# Test preserving a multiple space sequence across a line break
row_data = ['First last one']
row = tc.generate_row(row_data=row_data, is_header=False)
assert row == ('First \n'
' last one ')
def test_wrap_text_max_lines():
column_1 = Column("Col 1", width=10, max_data_lines=2)
tc = TableCreator([column_1])
# Test not needing to truncate the final line
row_data = ['First line last line']
row = tc.generate_row(row_data=row_data, is_header=False)
assert row == ('First line\n'
'last line ')
# Test having to truncate the last word because it's too long for the final line
row_data = ['First line last lineextratext']
row = tc.generate_row(row_data=row_data, is_header=False)
assert row == ('First line\n'
'last line…')
# Test having to truncate the last word because it fits the final line but there is more text not being included
row_data = ['First line thistxtfit extra']
row = tc.generate_row(row_data=row_data, is_header=False)
assert row == ('First line\n'
'thistxtfi…')
# Test having to truncate the last word because it fits the final line but there are more lines not being included
row_data = ['First line thistxtfit\nextra']
row = tc.generate_row(row_data=row_data, is_header=False)
assert row == ('First line\n'
'thistxtfi…')
# Test having space left on the final line and adding an ellipsis because there are more lines not being included
row_data = ['First line last line\nextra line']
row = tc.generate_row(row_data=row_data, is_header=False)
assert row == ('First line\n'
'last line…')
def test_wrap_long_word():
# Make sure words wider than column start on own line and wrap
column_1 = Column("LongColumnName", width=10)
column_2 = Column("Col 2", width=10)
columns = [column_1, column_2]
tc = TableCreator(columns)
# Test header row
row_data = [col.header for col in columns]
header = tc.generate_row(row_data, is_header=True)
assert header == ('LongColumn \n'
'Name Col 2 ')
# Test data row
row_data = list()
# Long word should start on the first line (style should not affect width)
row_data.append(ansi.style("LongerThan10", fg=Fg.GREEN))
# Long word should start on the second line
row_data.append("Word LongerThan10")
row = tc.generate_row(row_data=row_data, is_header=False)
expected = (
TextStyle.RESET_ALL
+ Fg.GREEN
+ "LongerThan"
+ TextStyle.RESET_ALL
+ " Word \n"
+ TextStyle.RESET_ALL
+ Fg.GREEN
+ "10"
+ Fg.RESET
+ TextStyle.RESET_ALL
+ ' '
+ TextStyle.RESET_ALL
+ ' LongerThan\n'
' 10 '
)
assert row == expected
def test_wrap_long_word_max_data_lines():
column_1 = Column("Col 1", width=10, max_data_lines=2)
column_2 = Column("Col 2", width=10, max_data_lines=2)
column_3 = Column("Col 3", width=10, max_data_lines=2)
column_4 = Column("Col 4", width=10, max_data_lines=1)
columns = [column_1, column_2, column_3, column_4]
tc = TableCreator(columns)
row_data = list()
# This long word will exactly fit the last line and it's the final word in the text. No ellipsis should appear.
row_data.append("LongerThan10FitsLast")
# This long word will exactly fit the last line but it's not the final word in the text.
# Make sure ellipsis word's final character.
row_data.append("LongerThan10FitsLast\nMore lines")
# This long word will run over the last line. Make sure it is truncated.
row_data.append("LongerThan10RunsOverLast")
# This long word will start on the final line after another word. Therefore it won't wrap but will instead be truncated.
row_data.append("A LongerThan10RunsOverLast")
row = tc.generate_row(row_data=row_data, is_header=False)
assert row == ('LongerThan LongerThan LongerThan A LongerT…\n'
'10FitsLast 10FitsLas… 10RunsOve… ')
def test_wrap_long_char_wider_than_max_width():
"""
This tests case where a character is wider than max_width. This can happen if max_width
is 1 and the text contains wide characters (e.g. East Asian). Replace it with an ellipsis.
"""
column_1 = Column("Col 1", width=1)
tc = TableCreator([column_1])
row = tc.generate_row(row_data=['深'], is_header=False)
assert row == '…'
def test_generate_row_exceptions():
column_1 = Column("Col 1")
tc = TableCreator([column_1])
row_data = ['fake']
# fill_char too long
with pytest.raises(TypeError) as excinfo:
tc.generate_row(row_data=row_data, is_header=False, fill_char='too long')
assert "Fill character must be exactly one character long" in str(excinfo.value)
# Unprintable characters
for arg in ['fill_char', 'pre_line', 'inter_cell', 'post_line']:
kwargs = {arg: '\n'}
with pytest.raises(ValueError) as excinfo:
tc.generate_row(row_data=row_data, is_header=False, **kwargs)
assert "{} contains an unprintable character".format(arg) in str(excinfo.value)
# Data with too many columns
row_data = ['Data 1', 'Extra Column']
with pytest.raises(ValueError) as excinfo:
tc.generate_row(row_data=row_data, is_header=False)
assert "Length of row_data must match length of cols" in str(excinfo.value)
def test_tabs():
column_1 = Column("Col\t1", width=20)
column_2 = Column("Col 2")
columns = [column_1, column_2]
tc = TableCreator(columns, tab_width=2)
row_data = [col.header for col in columns]
row = tc.generate_row(row_data, is_header=True, fill_char='\t', pre_line='\t', inter_cell='\t', post_line='\t')
assert row == ' Col 1 Col 2 '
with pytest.raises(ValueError) as excinfo:
TableCreator([column_1, column_2], tab_width=0)
assert "Tab width cannot be less than 1" in str(excinfo.value)
def test_simple_table_creation():
column_1 = Column("Col 1", width=16)
column_2 = Column("Col 2", width=16)
row_data = list()
row_data.append(["Col 1 Row 1", "Col 2 Row 1"])
row_data.append(["Col 1 Row 2", "Col 2 Row 2"])
# Default options
st = SimpleTable([column_1, column_2])
table = st.generate_table(row_data)
assert table == (
'Col 1 Col 2 \n'
'----------------------------------\n'
'Col 1 Row 1 Col 2 Row 1 \n'
' \n'
'Col 1 Row 2 Col 2 Row 2 '
)
# Custom column spacing
st = SimpleTable([column_1, column_2], column_spacing=5)
table = st.generate_table(row_data)
assert table == (
'Col 1 Col 2 \n'
'-------------------------------------\n'
'Col 1 Row 1 Col 2 Row 1 \n'
' \n'
'Col 1 Row 2 Col 2 Row 2 '
)
# Custom divider
st = SimpleTable([column_1, column_2], divider_char='─')
table = st.generate_table(row_data)
assert table == (
'Col 1 Col 2 \n'
'──────────────────────────────────\n'
'Col 1 Row 1 Col 2 Row 1 \n'
' \n'
'Col 1 Row 2 Col 2 Row 2 '
)
# No divider
st = SimpleTable([column_1, column_2], divider_char=None)
no_divider_1 = st.generate_table(row_data)
st = SimpleTable([column_1, column_2], divider_char='')
no_divider_2 = st.generate_table(row_data)
assert no_divider_1 == no_divider_2 == (
'Col 1 Col 2 \n'
'Col 1 Row 1 Col 2 Row 1 \n'
' \n'
'Col 1 Row 2 Col 2 Row 2 '
)
# No row spacing
st = SimpleTable([column_1, column_2])
table = st.generate_table(row_data, row_spacing=0)
assert table == (
'Col 1 Col 2 \n'
'----------------------------------\n'
'Col 1 Row 1 Col 2 Row 1 \n'
'Col 1 Row 2 Col 2 Row 2 '
)
# No header
st = SimpleTable([column_1, column_2])
table = st.generate_table(row_data, include_header=False)
assert table == ('Col 1 Row 1 Col 2 Row 1 \n'
' \n'
'Col 1 Row 2 Col 2 Row 2 ')
# Wide custom divider (divider needs no padding)
st = SimpleTable([column_1, column_2], divider_char='深')
table = st.generate_table(row_data)
assert table == (
'Col 1 Col 2 \n'
'深深深深深深深深深深深深深深深深深\n'
'Col 1 Row 1 Col 2 Row 1 \n'
' \n'
'Col 1 Row 2 Col 2 Row 2 '
)
# Wide custom divider (divider needs padding)
st = SimpleTable([column_1, Column("Col 2", width=17)],
divider_char='深')
table = st.generate_table(row_data)
assert table == (
'Col 1 Col 2 \n'
'深深深深深深深深深深深深深深深深深 \n'
'Col 1 Row 1 Col 2 Row 1 \n'
' \n'
'Col 1 Row 2 Col 2 Row 2 '
)
# Invalid column spacing
with pytest.raises(ValueError) as excinfo:
SimpleTable([column_1, column_2], column_spacing=-1)
assert "Column spacing cannot be less than 0" in str(excinfo.value)
# Invalid divider character
with pytest.raises(TypeError) as excinfo:
SimpleTable([column_1, column_2], divider_char='too long')
assert "Divider character must be exactly one character long" in str(excinfo.value)
with pytest.raises(ValueError) as excinfo:
SimpleTable([column_1, column_2], divider_char='\n')
assert "Divider character is an unprintable character" in str(excinfo.value)
# Invalid row spacing
st = SimpleTable([column_1, column_2])
with pytest.raises(ValueError) as excinfo:
st.generate_table(row_data, row_spacing=-1)
assert "Row spacing cannot be less than 0" in str(excinfo.value)
# Test header and data colors
st = SimpleTable([column_1, column_2], divider_char=None, header_bg=Bg.GREEN, data_bg=Bg.LIGHT_BLUE)
table = st.generate_table(row_data)
assert table == (
'\x1b[0m\x1b[42mCol 1\x1b[49m\x1b[0m\x1b[42m \x1b[49m\x1b[0m\x1b[42m \x1b[49m\x1b[0m\x1b[42mCol 2\x1b[49m\x1b[0m\x1b[42m \x1b[49m\x1b[0m\n'
'\x1b[0m\x1b[104mCol 1 Row 1\x1b[49m\x1b[0m\x1b[104m \x1b[49m\x1b[0m\x1b[104m \x1b[49m\x1b[0m\x1b[104mCol 2 Row 1\x1b[49m\x1b[0m\x1b[104m \x1b[49m\x1b[0m\n'
'\x1b[0m\x1b[0m\x1b[104m \x1b[49m\x1b[0m\n'
'\x1b[0m\x1b[104mCol 1 Row 2\x1b[49m\x1b[0m\x1b[104m \x1b[49m\x1b[0m\x1b[104m \x1b[49m\x1b[0m\x1b[104mCol 2 Row 2\x1b[49m\x1b[0m\x1b[104m \x1b[49m\x1b[0m'
)
# Make sure SimpleTable respects style_header_text and style_data_text flags.
# Don't apply parent table's background colors to header or data text in second column.
st = SimpleTable([column_1, Column("Col 2", width=16, style_header_text=False, style_data_text=False)],
divider_char=None, header_bg=Bg.GREEN, data_bg=Bg.LIGHT_BLUE)
table = st.generate_table(row_data)
assert table == (
'\x1b[0m\x1b[42mCol 1\x1b[49m\x1b[0m\x1b[42m \x1b[49m\x1b[0m\x1b[42m \x1b[49m\x1b[0mCol 2\x1b[0m\x1b[42m \x1b[49m\x1b[0m\n'
'\x1b[0m\x1b[104mCol 1 Row 1\x1b[49m\x1b[0m\x1b[104m \x1b[49m\x1b[0m\x1b[104m \x1b[49m\x1b[0mCol 2 Row 1\x1b[0m\x1b[104m \x1b[49m\x1b[0m\n'
'\x1b[0m\x1b[0m\x1b[104m \x1b[49m\x1b[0m\n'
'\x1b[0m\x1b[104mCol 1 Row 2\x1b[49m\x1b[0m\x1b[104m \x1b[49m\x1b[0m\x1b[104m \x1b[49m\x1b[0mCol 2 Row 2\x1b[0m\x1b[104m \x1b[49m\x1b[0m'
)
def test_simple_table_width():
# Base width
for num_cols in range(1, 10):
assert SimpleTable.base_width(num_cols) == (num_cols - 1) * 2
# Invalid num_cols value
with pytest.raises(ValueError) as excinfo:
SimpleTable.base_width(0)
assert "Column count cannot be less than 1" in str(excinfo.value)
# Total width
column_1 = Column("Col 1", width=16)
column_2 = Column("Col 2", width=16)
row_data = list()
row_data.append(["Col 1 Row 1", "Col 2 Row 1"])
row_data.append(["Col 1 Row 2", "Col 2 Row 2"])
st = SimpleTable([column_1, column_2])
assert st.total_width() == 34
def test_simple_generate_data_row_exceptions():
column_1 = Column("Col 1")
tc = SimpleTable([column_1])
# Data with too many columns
row_data = ['Data 1', 'Extra Column']
with pytest.raises(ValueError) as excinfo:
tc.generate_data_row(row_data=row_data)
assert "Length of row_data must match length of cols" in str(excinfo.value)
def test_bordered_table_creation():
column_1 = Column("Col 1", width=15)
column_2 = Column("Col 2", width=15)
row_data = list()
row_data.append(["Col 1 Row 1", "Col 2 Row 1"])
row_data.append(["Col 1 Row 2", "Col 2 Row 2"])
# Default options
bt = BorderedTable([column_1, column_2])
table = bt.generate_table(row_data)
assert table == (
'╔═════════════════╤═════════════════╗\n'
'║ Col 1 │ Col 2 ║\n'
'╠═════════════════╪═════════════════╣\n'
'║ Col 1 Row 1 │ Col 2 Row 1 ║\n'
'╟─────────────────┼─────────────────╢\n'
'║ Col 1 Row 2 │ Col 2 Row 2 ║\n'
'╚═════════════════╧═════════════════╝'
)
# No column borders
bt = BorderedTable([column_1, column_2], column_borders=False)
table = bt.generate_table(row_data)
assert table == (
'╔══════════════════════════════════╗\n'
'║ Col 1 Col 2 ║\n'
'╠══════════════════════════════════╣\n'
'║ Col 1 Row 1 Col 2 Row 1 ║\n'
'╟──────────────────────────────────╢\n'
'║ Col 1 Row 2 Col 2 Row 2 ║\n'
'╚══════════════════════════════════╝'
)
# No header
bt = BorderedTable([column_1, column_2])
table = bt.generate_table(row_data, include_header=False)
assert table == (
'╔═════════════════╤═════════════════╗\n'
'║ Col 1 Row 1 │ Col 2 Row 1 ║\n'
'╟─────────────────┼─────────────────╢\n'
'║ Col 1 Row 2 │ Col 2 Row 2 ║\n'
'╚═════════════════╧═════════════════╝'
)
# Non-default padding
bt = BorderedTable([column_1, column_2], padding=2)
table = bt.generate_table(row_data)
assert table == (
'╔═══════════════════╤═══════════════════╗\n'
'║ Col 1 │ Col 2 ║\n'
'╠═══════════════════╪═══════════════════╣\n'
'║ Col 1 Row 1 │ Col 2 Row 1 ║\n'
'╟───────────────────┼───────────────────╢\n'
'║ Col 1 Row 2 │ Col 2 Row 2 ║\n'
'╚═══════════════════╧═══════════════════╝'
)
# Invalid padding
with pytest.raises(ValueError) as excinfo:
BorderedTable([column_1, column_2], padding=-1)
assert "Padding cannot be less than 0" in str(excinfo.value)
# Test border, header, and data colors
bt = BorderedTable([column_1, column_2], border_fg=Fg.LIGHT_YELLOW, border_bg=Bg.WHITE,
header_bg=Bg.GREEN, data_bg=Bg.LIGHT_BLUE)
table = bt.generate_table(row_data)
assert table == (
'\x1b[93m\x1b[107m╔═\x1b[39m\x1b[49m\x1b[0m\x1b[0m\x1b[93m\x1b[107m═══════════════\x1b[39m\x1b[49m\x1b[0m\x1b[93m\x1b[107m═╤═\x1b[39m\x1b[49m\x1b[0m\x1b[0m\x1b[93m\x1b[107m═══════════════\x1b[39m\x1b[49m\x1b[0m\x1b[93m\x1b[107m═╗\x1b[39m\x1b[49m\n'
'\x1b[93m\x1b[107m║\x1b[39m\x1b[49m\x1b[42m \x1b[49m\x1b[0m\x1b[42mCol 1\x1b[49m\x1b[0m\x1b[42m \x1b[49m\x1b[0m\x1b[42m \x1b[49m\x1b[93m\x1b[107m│\x1b[39m\x1b[49m\x1b[42m \x1b[49m\x1b[0m\x1b[42mCol 2\x1b[49m\x1b[0m\x1b[42m \x1b[49m\x1b[0m\x1b[42m \x1b[49m\x1b[93m\x1b[107m║\x1b[39m\x1b[49m\n'
'\x1b[93m\x1b[107m╠═\x1b[39m\x1b[49m\x1b[0m\x1b[0m\x1b[93m\x1b[107m═══════════════\x1b[39m\x1b[49m\x1b[0m\x1b[93m\x1b[107m═╪═\x1b[39m\x1b[49m\x1b[0m\x1b[0m\x1b[93m\x1b[107m═══════════════\x1b[39m\x1b[49m\x1b[0m\x1b[93m\x1b[107m═╣\x1b[39m\x1b[49m\n'
'\x1b[93m\x1b[107m║\x1b[39m\x1b[49m\x1b[104m \x1b[49m\x1b[0m\x1b[104mCol 1 Row 1\x1b[49m\x1b[0m\x1b[104m \x1b[49m\x1b[0m\x1b[104m \x1b[49m\x1b[93m\x1b[107m│\x1b[39m\x1b[49m\x1b[104m \x1b[49m\x1b[0m\x1b[104mCol 2 Row 1\x1b[49m\x1b[0m\x1b[104m \x1b[49m\x1b[0m\x1b[104m \x1b[49m\x1b[93m\x1b[107m║\x1b[39m\x1b[49m\n'
'\x1b[93m\x1b[107m╟─\x1b[39m\x1b[49m\x1b[0m\x1b[0m\x1b[93m\x1b[107m───────────────\x1b[39m\x1b[49m\x1b[0m\x1b[93m\x1b[107m─┼─\x1b[39m\x1b[49m\x1b[0m\x1b[0m\x1b[93m\x1b[107m───────────────\x1b[39m\x1b[49m\x1b[0m\x1b[93m\x1b[107m─╢\x1b[39m\x1b[49m\n'
'\x1b[93m\x1b[107m║\x1b[39m\x1b[49m\x1b[104m \x1b[49m\x1b[0m\x1b[104mCol 1 Row 2\x1b[49m\x1b[0m\x1b[104m \x1b[49m\x1b[0m\x1b[104m \x1b[49m\x1b[93m\x1b[107m│\x1b[39m\x1b[49m\x1b[104m \x1b[49m\x1b[0m\x1b[104mCol 2 Row 2\x1b[49m\x1b[0m\x1b[104m \x1b[49m\x1b[0m\x1b[104m \x1b[49m\x1b[93m\x1b[107m║\x1b[39m\x1b[49m\n'
'\x1b[93m\x1b[107m╚═\x1b[39m\x1b[49m\x1b[0m\x1b[0m\x1b[93m\x1b[107m═══════════════\x1b[39m\x1b[49m\x1b[0m\x1b[93m\x1b[107m═╧═\x1b[39m\x1b[49m\x1b[0m\x1b[0m\x1b[93m\x1b[107m═══════════════\x1b[39m\x1b[49m\x1b[0m\x1b[93m\x1b[107m═╝\x1b[39m\x1b[49m'
)
# Make sure BorderedTable respects style_header_text and style_data_text flags.
# Don't apply parent table's background colors to header or data text in second column.
bt = BorderedTable([column_1, Column("Col 2", width=15, style_header_text=False, style_data_text=False)],
header_bg=Bg.GREEN, data_bg=Bg.LIGHT_BLUE)
table = bt.generate_table(row_data)
assert table == (
'╔═════════════════╤═════════════════╗\n'
'║\x1b[42m \x1b[49m\x1b[0m\x1b[42mCol 1\x1b[49m\x1b[0m\x1b[42m \x1b[49m\x1b[0m\x1b[42m \x1b[49m│\x1b[42m \x1b[49m\x1b[0mCol 2\x1b[0m\x1b[42m \x1b[49m\x1b[0m\x1b[42m \x1b[49m║\n'
'╠═════════════════╪═════════════════╣\n'
'║\x1b[104m \x1b[49m\x1b[0m\x1b[104mCol 1 Row 1\x1b[49m\x1b[0m\x1b[104m \x1b[49m\x1b[0m\x1b[104m \x1b[49m│\x1b[104m \x1b[49m\x1b[0mCol 2 Row 1\x1b[0m\x1b[104m \x1b[49m\x1b[0m\x1b[104m \x1b[49m║\n'
'╟─────────────────┼─────────────────╢\n'
'║\x1b[104m \x1b[49m\x1b[0m\x1b[104mCol 1 Row 2\x1b[49m\x1b[0m\x1b[104m \x1b[49m\x1b[0m\x1b[104m \x1b[49m│\x1b[104m \x1b[49m\x1b[0mCol 2 Row 2\x1b[0m\x1b[104m \x1b[49m\x1b[0m\x1b[104m \x1b[49m║\n'
'╚═════════════════╧═════════════════╝'
)
def test_bordered_table_width():
# Default behavior (column_borders=True, padding=1)
assert BorderedTable.base_width(1) == 4
assert BorderedTable.base_width(2) == 7
assert BorderedTable.base_width(3) == 10
# No column borders
assert BorderedTable.base_width(1, column_borders=False) == 4
assert BorderedTable.base_width(2, column_borders=False) == 6
assert BorderedTable.base_width(3, column_borders=False) == 8
# No padding
assert BorderedTable.base_width(1, padding=0) == 2
assert BorderedTable.base_width(2, padding=0) == 3
assert BorderedTable.base_width(3, padding=0) == 4
# Extra padding
assert BorderedTable.base_width(1, padding=3) == 8
assert BorderedTable.base_width(2, padding=3) == 15
assert BorderedTable.base_width(3, padding=3) == 22
# Invalid num_cols value
with pytest.raises(ValueError) as excinfo:
BorderedTable.base_width(0)
assert "Column count cannot be less than 1" in str(excinfo.value)
# Total width
column_1 = Column("Col 1", width=15)
column_2 = Column("Col 2", width=15)
row_data = list()
row_data.append(["Col 1 Row 1", "Col 2 Row 1"])
row_data.append(["Col 1 Row 2", "Col 2 Row 2"])
bt = BorderedTable([column_1, column_2])
assert bt.total_width() == 37
def test_bordered_generate_data_row_exceptions():
column_1 = Column("Col 1")
tc = BorderedTable([column_1])
# Data with too many columns
row_data = ['Data 1', 'Extra Column']
with pytest.raises(ValueError) as excinfo:
tc.generate_data_row(row_data=row_data)
assert "Length of row_data must match length of cols" in str(excinfo.value)
def test_alternating_table_creation():
column_1 = Column("Col 1", width=15)
column_2 = Column("Col 2", width=15)
row_data = list()
row_data.append(["Col 1 Row 1", "Col 2 Row 1"])
row_data.append(["Col 1 Row 2", "Col 2 Row 2"])
# Default options
at = AlternatingTable([column_1, column_2])
table = at.generate_table(row_data)
assert table == (
'╔═════════════════╤═════════════════╗\n'
'║ Col 1 │ Col 2 ║\n'
'╠═════════════════╪═════════════════╣\n'
'║ Col 1 Row 1 │ Col 2 Row 1 ║\n'
'║\x1b[100m \x1b[49m\x1b[0m\x1b[100mCol 1 Row 2\x1b[49m\x1b[0m\x1b[100m \x1b[49m\x1b[0m\x1b[100m \x1b[49m│\x1b[100m \x1b[49m\x1b[0m\x1b[100mCol 2 Row 2\x1b[49m\x1b[0m\x1b[100m \x1b[49m\x1b[0m\x1b[100m \x1b[49m║\n'
'╚═════════════════╧═════════════════╝'
)
# No column borders
at = AlternatingTable([column_1, column_2], column_borders=False)
table = at.generate_table(row_data)
assert table == (
'╔══════════════════════════════════╗\n'
'║ Col 1 Col 2 ║\n'
'╠══════════════════════════════════╣\n'
'║ Col 1 Row 1 Col 2 Row 1 ║\n'
'║\x1b[100m \x1b[49m\x1b[0m\x1b[100mCol 1 Row 2\x1b[49m\x1b[0m\x1b[100m \x1b[49m\x1b[0m\x1b[100m \x1b[49m\x1b[100m \x1b[49m\x1b[0m\x1b[100mCol 2 Row 2\x1b[49m\x1b[0m\x1b[100m \x1b[49m\x1b[0m\x1b[100m \x1b[49m║\n'
'╚══════════════════════════════════╝'
)
# No header
at = AlternatingTable([column_1, column_2])
table = at.generate_table(row_data, include_header=False)
assert table == (
'╔═════════════════╤═════════════════╗\n'
'║ Col 1 Row 1 │ Col 2 Row 1 ║\n'
'║\x1b[100m \x1b[49m\x1b[0m\x1b[100mCol 1 Row 2\x1b[49m\x1b[0m\x1b[100m \x1b[49m\x1b[0m\x1b[100m \x1b[49m│\x1b[100m \x1b[49m\x1b[0m\x1b[100mCol 2 Row 2\x1b[49m\x1b[0m\x1b[100m \x1b[49m\x1b[0m\x1b[100m \x1b[49m║\n'
'╚═════════════════╧═════════════════╝'
)
# Non-default padding
at = AlternatingTable([column_1, column_2], padding=2)
table = at.generate_table(row_data)
assert table == (
'╔═══════════════════╤═══════════════════╗\n'
'║ Col 1 │ Col 2 ║\n'
'╠═══════════════════╪═══════════════════╣\n'
'║ Col 1 Row 1 │ Col 2 Row 1 ║\n'
'║\x1b[100m \x1b[49m\x1b[0m\x1b[100mCol 1 Row 2\x1b[49m\x1b[0m\x1b[100m \x1b[49m\x1b[0m\x1b[100m \x1b[49m│\x1b[100m \x1b[49m\x1b[0m\x1b[100mCol 2 Row 2\x1b[49m\x1b[0m\x1b[100m \x1b[49m\x1b[0m\x1b[100m \x1b[49m║\n'
'╚═══════════════════╧═══════════════════╝'
)
# Invalid padding
with pytest.raises(ValueError) as excinfo:
AlternatingTable([column_1, column_2], padding=-1)
assert "Padding cannot be less than 0" in str(excinfo.value)
# Test border, header, and data colors
at = AlternatingTable([column_1, column_2], border_fg=Fg.LIGHT_YELLOW, border_bg=Bg.WHITE,
header_bg=Bg.GREEN, odd_bg=Bg.LIGHT_BLUE, even_bg=Bg.LIGHT_RED)
table = at.generate_table(row_data)
assert table == (
'\x1b[93m\x1b[107m╔═\x1b[39m\x1b[49m\x1b[0m\x1b[0m\x1b[93m\x1b[107m═══════════════\x1b[39m\x1b[49m\x1b[0m\x1b[93m\x1b[107m═╤═\x1b[39m\x1b[49m\x1b[0m\x1b[0m\x1b[93m\x1b[107m═══════════════\x1b[39m\x1b[49m\x1b[0m\x1b[93m\x1b[107m═╗\x1b[39m\x1b[49m\n'
'\x1b[93m\x1b[107m║\x1b[39m\x1b[49m\x1b[42m \x1b[49m\x1b[0m\x1b[42mCol 1\x1b[49m\x1b[0m\x1b[42m \x1b[49m\x1b[0m\x1b[42m \x1b[49m\x1b[93m\x1b[107m│\x1b[39m\x1b[49m\x1b[42m \x1b[49m\x1b[0m\x1b[42mCol 2\x1b[49m\x1b[0m\x1b[42m \x1b[49m\x1b[0m\x1b[42m \x1b[49m\x1b[93m\x1b[107m║\x1b[39m\x1b[49m\n'
'\x1b[93m\x1b[107m╠═\x1b[39m\x1b[49m\x1b[0m\x1b[0m\x1b[93m\x1b[107m═══════════════\x1b[39m\x1b[49m\x1b[0m\x1b[93m\x1b[107m═╪═\x1b[39m\x1b[49m\x1b[0m\x1b[0m\x1b[93m\x1b[107m═══════════════\x1b[39m\x1b[49m\x1b[0m\x1b[93m\x1b[107m═╣\x1b[39m\x1b[49m\n'
'\x1b[93m\x1b[107m║\x1b[39m\x1b[49m\x1b[104m \x1b[49m\x1b[0m\x1b[104mCol 1 Row 1\x1b[49m\x1b[0m\x1b[104m \x1b[49m\x1b[0m\x1b[104m \x1b[49m\x1b[93m\x1b[107m│\x1b[39m\x1b[49m\x1b[104m \x1b[49m\x1b[0m\x1b[104mCol 2 Row 1\x1b[49m\x1b[0m\x1b[104m \x1b[49m\x1b[0m\x1b[104m \x1b[49m\x1b[93m\x1b[107m║\x1b[39m\x1b[49m\n'
'\x1b[93m\x1b[107m║\x1b[39m\x1b[49m\x1b[101m \x1b[49m\x1b[0m\x1b[101mCol 1 Row 2\x1b[49m\x1b[0m\x1b[101m \x1b[49m\x1b[0m\x1b[101m \x1b[49m\x1b[93m\x1b[107m│\x1b[39m\x1b[49m\x1b[101m \x1b[49m\x1b[0m\x1b[101mCol 2 Row 2\x1b[49m\x1b[0m\x1b[101m \x1b[49m\x1b[0m\x1b[101m \x1b[49m\x1b[93m\x1b[107m║\x1b[39m\x1b[49m\n'
'\x1b[93m\x1b[107m╚═\x1b[39m\x1b[49m\x1b[0m\x1b[0m\x1b[93m\x1b[107m═══════════════\x1b[39m\x1b[49m\x1b[0m\x1b[93m\x1b[107m═╧═\x1b[39m\x1b[49m\x1b[0m\x1b[0m\x1b[93m\x1b[107m═══════════════\x1b[39m\x1b[49m\x1b[0m\x1b[93m\x1b[107m═╝\x1b[39m\x1b[49m'
)
# Make sure AlternatingTable respects style_header_text and style_data_text flags.
# Don't apply parent table's background colors to header or data text in second column.
at = AlternatingTable([column_1, Column("Col 2", width=15, style_header_text=False, style_data_text=False)],
header_bg=Bg.GREEN, odd_bg=Bg.LIGHT_BLUE, even_bg=Bg.LIGHT_RED)
table = at.generate_table(row_data)
assert table == (
'╔═════════════════╤═════════════════╗\n'
'║\x1b[42m \x1b[49m\x1b[0m\x1b[42mCol 1\x1b[49m\x1b[0m\x1b[42m \x1b[49m\x1b[0m\x1b[42m \x1b[49m│\x1b[42m \x1b[49m\x1b[0mCol 2\x1b[0m\x1b[42m \x1b[49m\x1b[0m\x1b[42m \x1b[49m║\n'
'╠═════════════════╪═════════════════╣\n'
'║\x1b[104m \x1b[49m\x1b[0m\x1b[104mCol 1 Row 1\x1b[49m\x1b[0m\x1b[104m \x1b[49m\x1b[0m\x1b[104m \x1b[49m│\x1b[104m \x1b[49m\x1b[0mCol 2 Row 1\x1b[0m\x1b[104m \x1b[49m\x1b[0m\x1b[104m \x1b[49m║\n'
'║\x1b[101m \x1b[49m\x1b[0m\x1b[101mCol 1 Row 2\x1b[49m\x1b[0m\x1b[101m \x1b[49m\x1b[0m\x1b[101m \x1b[49m│\x1b[101m \x1b[49m\x1b[0mCol 2 Row 2\x1b[0m\x1b[101m \x1b[49m\x1b[0m\x1b[101m \x1b[49m║\n'
'╚═════════════════╧═════════════════╝'
)
|