|
| 1 | +# coding=utf-8 |
| 2 | +""" |
| 3 | +Unit testing the variety of column customizations available |
| 4 | +""" |
| 5 | +import pytest |
| 6 | + |
| 7 | +import tableformatter as tf |
| 8 | + |
| 9 | +# Make the test results reproducible regardless of what color libraries are installed |
| 10 | +tf.TableColors.set_color_library('none') |
| 11 | +tf.set_default_grid(tf.AlternatingRowGrid('', '')) |
| 12 | + |
| 13 | + |
| 14 | +class MyRowObject(object): |
| 15 | + """Simple object to demonstrate using a list of objects with TableFormatter""" |
| 16 | + def __init__(self, field1: str, field2: str, field3: int, field4: int): |
| 17 | + self.field1 = field1 |
| 18 | + self.field2 = field2 |
| 19 | + self._field3 = field3 |
| 20 | + self.field4 = field4 |
| 21 | + |
| 22 | + def get_field3(self): |
| 23 | + """Demonstrates accessing object functions""" |
| 24 | + return self._field3 |
| 25 | + |
| 26 | + |
| 27 | +def multiply(row_obj: MyRowObject): |
| 28 | + """Demonstrates an object formatter function""" |
| 29 | + return str(row_obj.get_field3() * row_obj.field4) |
| 30 | + |
| 31 | + |
| 32 | +def int2word(num, separator="-"): |
| 33 | + """Demonstrates a field formatter function |
| 34 | + From: https://codereview.stackexchange.com/questions/156590/create-the-english-word-for-a-number |
| 35 | + """ |
| 36 | + ones_and_teens = {0: "Zero", 1: 'One', 2: 'Two', 3: 'Three', |
| 37 | + 4: 'Four', 5: 'Five', 6: 'Six', 7: 'Seven', |
| 38 | + 8: 'Eight', 9: 'Nine', 10: 'Ten', 11: 'Eleven', |
| 39 | + 12: 'Twelve', 13: 'Thirteen', 14: 'Fourteen', |
| 40 | + 15: 'Fifteen', 16: 'Sixteen', 17: 'Seventeen', |
| 41 | + 18: 'Eighteen', 19: 'Nineteen'} |
| 42 | + twenty2ninety = {2: 'Twenty', 3: 'Thirty', 4: 'Forty', 5: 'Fifty', |
| 43 | + 6: 'Sixty', 7: 'Seventy', 8: 'Eighty', 9: 'Ninety', 0: ""} |
| 44 | + |
| 45 | + if 0 <= num < 19: |
| 46 | + return ones_and_teens[num] |
| 47 | + elif 20 <= num <= 99: |
| 48 | + tens, below_ten = divmod(num, 10) |
| 49 | + if below_ten > 0: |
| 50 | + words = twenty2ninety[tens] + separator + \ |
| 51 | + ones_and_teens[below_ten].lower() |
| 52 | + else: |
| 53 | + words = twenty2ninety[tens] |
| 54 | + return words |
| 55 | + |
| 56 | + elif 100 <= num <= 999: |
| 57 | + hundreds, below_hundred = divmod(num, 100) |
| 58 | + tens, below_ten = divmod(below_hundred, 10) |
| 59 | + if below_hundred == 0: |
| 60 | + words = ones_and_teens[hundreds] + separator + "hundred" |
| 61 | + elif below_ten == 0: |
| 62 | + words = ones_and_teens[hundreds] + separator + \ |
| 63 | + "hundred" + separator + twenty2ninety[tens].lower() |
| 64 | + else: |
| 65 | + if tens > 0: |
| 66 | + words = ones_and_teens[hundreds] + separator + "hundred" + separator + twenty2ninety[ |
| 67 | + tens].lower() + separator + ones_and_teens[below_ten].lower() |
| 68 | + else: |
| 69 | + words = ones_and_teens[ |
| 70 | + hundreds] + separator + "hundred" + separator + ones_and_teens[below_ten].lower() |
| 71 | + return words |
| 72 | + |
| 73 | + else: |
| 74 | + print("num out of range") |
| 75 | + |
| 76 | + |
| 77 | +@pytest.fixture |
| 78 | +def obj_rows(): |
| 79 | + rows = [MyRowObject('Longer text that will trigger the column wrapping', 'A2', 5, 56), |
| 80 | + MyRowObject('B1', 'B2\nB2\nB2', 23, 8), |
| 81 | + MyRowObject('C1', 'C2', 4, 9), |
| 82 | + MyRowObject('D1', 'D2', 7, 5)] |
| 83 | + return rows |
| 84 | + |
| 85 | + |
| 86 | +def test_wrapped_object_formatter(obj_rows): |
| 87 | + columns = (tf.Column('First', width=20, attrib='field1'), |
| 88 | + tf.Column('Second', attrib='field2'), |
| 89 | + tf.Column('Num 1', width=3, attrib='get_field3'), |
| 90 | + tf.Column('Num 2', attrib='field4'), |
| 91 | + tf.Column('Multiplied', obj_formatter=multiply)) |
| 92 | + table = tf.generate_table(obj_rows, columns) |
| 93 | + expected = ''' |
| 94 | +╔══════════════════════╤════════╤═════╤═══════╤════════════╗ |
| 95 | +║ │ │ Num │ │ ║ |
| 96 | +║ First │ Second │ 1 │ Num 2 │ Multiplied ║ |
| 97 | +╠══════════════════════╪════════╪═════╪═══════╪════════════╣ |
| 98 | +║ Longer text that │ A2 │ 5 │ 56 │ 280 ║ |
| 99 | +║ will trigger the │ │ │ │ ║ |
| 100 | +║ column wrapping │ │ │ │ ║ |
| 101 | +║ B1 │ B2 │ 23 │ 8 │ 184 ║ |
| 102 | +║ │ B2 │ │ │ ║ |
| 103 | +║ │ B2 │ │ │ ║ |
| 104 | +║ C1 │ C2 │ 4 │ 9 │ 36 ║ |
| 105 | +║ D1 │ D2 │ 7 │ 5 │ 35 ║ |
| 106 | +╚══════════════════════╧════════╧═════╧═══════╧════════════╝ |
| 107 | +'''.lstrip('\n') |
| 108 | + assert table == expected |
| 109 | + |
| 110 | + |
| 111 | +def test_wrapped_indent_center_header(obj_rows): |
| 112 | + columns = (tf.Column('First', width=20, attrib='field1', wrap_mode=tf.WrapMode.WRAP_WITH_INDENT), |
| 113 | + tf.Column('Second', attrib='field2'), |
| 114 | + tf.Column('Num 1', width=3, attrib='get_field3', header_halign=tf.ColumnAlignment.AlignCenter), |
| 115 | + tf.Column('Num 2', attrib='field4'), |
| 116 | + tf.Column('Multiplied', attrib=None, obj_formatter=multiply)) |
| 117 | + table = tf.generate_table(obj_rows, columns) |
| 118 | + expected = ''' |
| 119 | +╔══════════════════════╤════════╤═════╤═══════╤════════════╗ |
| 120 | +║ │ │ Num │ │ ║ |
| 121 | +║ First │ Second │ 1 │ Num 2 │ Multiplied ║ |
| 122 | +╠══════════════════════╪════════╪═════╪═══════╪════════════╣ |
| 123 | +║ Longer text that │ A2 │ 5 │ 56 │ 280 ║ |
| 124 | +║ » will trigger the │ │ │ │ ║ |
| 125 | +║ » column wrapping │ │ │ │ ║ |
| 126 | +║ B1 │ B2 │ 23 │ 8 │ 184 ║ |
| 127 | +║ │ B2 │ │ │ ║ |
| 128 | +║ │ B2 │ │ │ ║ |
| 129 | +║ C1 │ C2 │ 4 │ 9 │ 36 ║ |
| 130 | +║ D1 │ D2 │ 7 │ 5 │ 35 ║ |
| 131 | +╚══════════════════════╧════════╧═════╧═══════╧════════════╝ |
| 132 | +'''.lstrip('\n') |
| 133 | + assert table == expected |
| 134 | + |
| 135 | + |
| 136 | +def test_wrapped_custom_indent_header_right_header_top(obj_rows): |
| 137 | + columns = (tf.Column('First', width=20, attrib='field1', wrap_mode=tf.WrapMode.WRAP_WITH_INDENT, |
| 138 | + wrap_prefix='>>> '), |
| 139 | + tf.Column('Second', attrib='field2', cell_halign=tf.ColumnAlignment.AlignCenter), |
| 140 | + tf.Column('Num 1', width=3, attrib='get_field3', header_halign=tf.ColumnAlignment.AlignRight), |
| 141 | + tf.Column('Num 2', attrib='field4', header_valign=tf.ColumnAlignment.AlignTop), |
| 142 | + tf.Column('Multiplied', attrib=None, obj_formatter=multiply)) |
| 143 | + table = tf.generate_table(obj_rows, columns) |
| 144 | + expected = ''' |
| 145 | +╔══════════════════════╤════════╤═════╤═══════╤════════════╗ |
| 146 | +║ │ │ Num │ Num 2 │ ║ |
| 147 | +║ First │ Second │ 1 │ │ Multiplied ║ |
| 148 | +╠══════════════════════╪════════╪═════╪═══════╪════════════╣ |
| 149 | +║ Longer text that │ A2 │ 5 │ 56 │ 280 ║ |
| 150 | +║ >>> will trigger the │ │ │ │ ║ |
| 151 | +║ >>> column wrapping │ │ │ │ ║ |
| 152 | +║ B1 │ B2 │ 23 │ 8 │ 184 ║ |
| 153 | +║ │ B2 │ │ │ ║ |
| 154 | +║ │ B2 │ │ │ ║ |
| 155 | +║ C1 │ C2 │ 4 │ 9 │ 36 ║ |
| 156 | +║ D1 │ D2 │ 7 │ 5 │ 35 ║ |
| 157 | +╚══════════════════════╧════════╧═════╧═══════╧════════════╝ |
| 158 | +'''.lstrip('\n') |
| 159 | + assert table == expected |
| 160 | + |
| 161 | + |
| 162 | +def test_truncate_end_custom_padding(obj_rows): |
| 163 | + columns = (tf.Column('First', width=20, attrib='field1', wrap_mode=tf.WrapMode.TRUNCATE_END), |
| 164 | + tf.Column('Second', attrib='field2', cell_padding=3), |
| 165 | + tf.Column('Num 1', width=3, attrib='get_field3'), |
| 166 | + tf.Column('Num 2', attrib='field4'), |
| 167 | + tf.Column('Multiplied', attrib=None, obj_formatter=multiply)) |
| 168 | + table = tf.generate_table(obj_rows, columns) |
| 169 | + expected = ''' |
| 170 | +╔══════════════════════╤════════════╤═════╤═══════╤════════════╗ |
| 171 | +║ │ │ Num │ │ ║ |
| 172 | +║ First │ Second │ 1 │ Num 2 │ Multiplied ║ |
| 173 | +╠══════════════════════╪════════════╪═════╪═══════╪════════════╣ |
| 174 | +║ Longer text that wi… │ A2 │ 5 │ 56 │ 280 ║ |
| 175 | +║ B1 │ B2 │ 23 │ 8 │ 184 ║ |
| 176 | +║ │ B2 │ │ │ ║ |
| 177 | +║ │ B2 │ │ │ ║ |
| 178 | +║ C1 │ C2 │ 4 │ 9 │ 36 ║ |
| 179 | +║ D1 │ D2 │ 7 │ 5 │ 35 ║ |
| 180 | +╚══════════════════════╧════════════╧═════╧═══════╧════════════╝ |
| 181 | +'''.lstrip('\n') |
| 182 | + assert table == expected |
| 183 | + |
| 184 | + |
| 185 | +def test_truncate_front_custom_padding_cell_align_right(obj_rows): |
| 186 | + columns = (tf.Column('First', width=20, attrib='field1', wrap_mode=tf.WrapMode.TRUNCATE_FRONT), |
| 187 | + tf.Column('Second', attrib='field2', cell_padding=5, cell_halign=tf.ColumnAlignment.AlignRight), |
| 188 | + tf.Column('Num 1', attrib='get_field3'), |
| 189 | + tf.Column('Num 2', attrib='field4'), |
| 190 | + tf.Column('Multiplied', attrib=None, obj_formatter=multiply)) |
| 191 | + table = tf.generate_table(obj_rows, columns) |
| 192 | + expected = ''' |
| 193 | +╔══════════════════════╤════════════════╤═══════╤═══════╤════════════╗ |
| 194 | +║ First │ Second │ Num 1 │ Num 2 │ Multiplied ║ |
| 195 | +╠══════════════════════╪════════════════╪═══════╪═══════╪════════════╣ |
| 196 | +║ …the column wrapping │ A2 │ 5 │ 56 │ 280 ║ |
| 197 | +║ B1 │ B2 │ 23 │ 8 │ 184 ║ |
| 198 | +║ │ B2 │ │ │ ║ |
| 199 | +║ │ B2 │ │ │ ║ |
| 200 | +║ C1 │ C2 │ 4 │ 9 │ 36 ║ |
| 201 | +║ D1 │ D2 │ 7 │ 5 │ 35 ║ |
| 202 | +╚══════════════════════╧════════════════╧═══════╧═══════╧════════════╝ |
| 203 | +'''.lstrip('\n') |
| 204 | + assert table == expected |
| 205 | + |
| 206 | + |
| 207 | +def test_truncate_middle_cell_align_bottom(obj_rows): |
| 208 | + columns = (tf.Column('First', width=20, attrib='field1', wrap_mode=tf.WrapMode.TRUNCATE_MIDDLE), |
| 209 | + tf.Column('Second', attrib='field2'), |
| 210 | + tf.Column('Num 1', attrib='get_field3'), |
| 211 | + tf.Column('Num 2', attrib='field4', cell_valign=tf.ColumnAlignment.AlignBottom), |
| 212 | + tf.Column('Multiplied', attrib=None, obj_formatter=multiply)) |
| 213 | + table = tf.generate_table(obj_rows, columns) |
| 214 | + expected = ''' |
| 215 | +╔══════════════════════╤════════╤═══════╤═══════╤════════════╗ |
| 216 | +║ First │ Second │ Num 1 │ Num 2 │ Multiplied ║ |
| 217 | +╠══════════════════════╪════════╪═══════╪═══════╪════════════╣ |
| 218 | +║ Longer t … wrapping │ A2 │ 5 │ 56 │ 280 ║ |
| 219 | +║ B1 │ B2 │ 23 │ │ 184 ║ |
| 220 | +║ │ B2 │ │ │ ║ |
| 221 | +║ │ B2 │ │ 8 │ ║ |
| 222 | +║ C1 │ C2 │ 4 │ 9 │ 36 ║ |
| 223 | +║ D1 │ D2 │ 7 │ 5 │ 35 ║ |
| 224 | +╚══════════════════════╧════════╧═══════╧═══════╧════════════╝ |
| 225 | +'''.lstrip('\n') |
| 226 | + assert table == expected |
| 227 | + |
| 228 | + |
| 229 | +def test_truncate_hard_field_formatter(obj_rows): |
| 230 | + columns = (tf.Column('First', width=20, attrib='field1', wrap_mode=tf.WrapMode.TRUNCATE_HARD), |
| 231 | + tf.Column('Second', attrib='field2'), |
| 232 | + tf.Column('Num 1', attrib='get_field3'), |
| 233 | + tf.Column('Num 2', attrib='field4', formatter=int2word), |
| 234 | + tf.Column('Multiplied', attrib=None, obj_formatter=multiply)) |
| 235 | + table = tf.generate_table(obj_rows, columns) |
| 236 | + expected = ''' |
| 237 | +╔══════════════════════╤════════╤═══════╤═══════════╤════════════╗ |
| 238 | +║ First │ Second │ Num 1 │ Num 2 │ Multiplied ║ |
| 239 | +╠══════════════════════╪════════╪═══════╪═══════════╪════════════╣ |
| 240 | +║ Longer text that wil │ A2 │ 5 │ Fifty-six │ 280 ║ |
| 241 | +║ B1 │ B2 │ 23 │ Eight │ 184 ║ |
| 242 | +║ │ B2 │ │ │ ║ |
| 243 | +║ │ B2 │ │ │ ║ |
| 244 | +║ C1 │ C2 │ 4 │ Nine │ 36 ║ |
| 245 | +║ D1 │ D2 │ 7 │ Five │ 35 ║ |
| 246 | +╚══════════════════════╧════════╧═══════╧═══════════╧════════════╝ |
| 247 | +'''.lstrip('\n') |
| 248 | + assert table == expected |
| 249 | + |
0 commit comments