398 lines
12 KiB
Python
398 lines
12 KiB
Python
"""
|
|
Unit tests for table column width optimization.
|
|
"""
|
|
|
|
import pytest
|
|
from pyWebLayout.layout.table_optimizer import (
|
|
optimize_table_layout,
|
|
sample_table_rows,
|
|
extract_html_column_widths,
|
|
parse_html_width,
|
|
distribute_column_widths,
|
|
get_column_count,
|
|
calculate_table_overhead
|
|
)
|
|
from pyWebLayout.abstract.block import Table
|
|
from pyWebLayout.concrete.table import TableStyle
|
|
|
|
|
|
class TestParseHtmlWidth:
|
|
"""Test HTML width parsing."""
|
|
|
|
def test_parse_int(self):
|
|
assert parse_html_width(100) == 100
|
|
|
|
def test_parse_px_string(self):
|
|
assert parse_html_width("150px") == 150
|
|
|
|
def test_parse_plain_number_string(self):
|
|
assert parse_html_width("200") == 200
|
|
|
|
def test_parse_percentage_returns_none(self):
|
|
assert parse_html_width("50%") is None
|
|
|
|
def test_parse_invalid_string(self):
|
|
assert parse_html_width("invalid") is None
|
|
|
|
def test_parse_with_whitespace(self):
|
|
assert parse_html_width(" 120px ") == 120
|
|
|
|
|
|
class TestDistributeColumnWidths:
|
|
"""Test column width distribution."""
|
|
|
|
def test_distribute_with_no_fixed_columns(self):
|
|
min_widths = [50, 60, 70]
|
|
pref_widths = [100, 120, 140]
|
|
available = 360
|
|
fixed = {}
|
|
|
|
result = distribute_column_widths(min_widths, pref_widths, available, fixed)
|
|
|
|
# Should use preferred widths (they fit)
|
|
assert result == [100, 120, 140]
|
|
|
|
def test_distribute_when_preferred_fits(self):
|
|
min_widths = [50, 50]
|
|
pref_widths = [100, 100]
|
|
available = 250
|
|
fixed = {}
|
|
|
|
result = distribute_column_widths(min_widths, pref_widths, available, fixed)
|
|
|
|
# Preferred widths fit, extra 50px distributed proportionally (25px each)
|
|
assert result == [125, 125]
|
|
|
|
def test_distribute_when_must_use_minimum(self):
|
|
min_widths = [100, 100]
|
|
pref_widths = [200, 200]
|
|
available = 150
|
|
fixed = {}
|
|
|
|
result = distribute_column_widths(min_widths, pref_widths, available, fixed)
|
|
|
|
# Can't even fit minimum, but force it anyway
|
|
assert result == [100, 100]
|
|
|
|
def test_distribute_proportional(self):
|
|
min_widths = [50, 50]
|
|
pref_widths = [200, 100]
|
|
available = 200 # Between min and pref totals
|
|
fixed = {}
|
|
|
|
result = distribute_column_widths(min_widths, pref_widths, available, fixed)
|
|
|
|
# Should distribute proportionally
|
|
assert len(result) == 2
|
|
assert result[0] + result[1] == 200
|
|
# First column should get more (higher pref)
|
|
assert result[0] > result[1]
|
|
|
|
def test_distribute_with_fixed_columns(self):
|
|
min_widths = [50, 50, 50]
|
|
pref_widths = [100, 100, 100]
|
|
available = 300
|
|
fixed = {1: 80} # Second column fixed at 80
|
|
|
|
result = distribute_column_widths(min_widths, pref_widths, available, fixed)
|
|
|
|
# Second column should be 80
|
|
assert result[1] == 80
|
|
# Other columns share remaining space
|
|
assert result[0] + result[2] == 220
|
|
|
|
def test_distribute_all_fixed(self):
|
|
min_widths = [50, 50]
|
|
pref_widths = [100, 100]
|
|
available = 300
|
|
fixed = {0: 120, 1: 150}
|
|
|
|
result = distribute_column_widths(min_widths, pref_widths, available, fixed)
|
|
|
|
assert result == [120, 150]
|
|
|
|
def test_distribute_empty(self):
|
|
result = distribute_column_widths([], [], 100, {})
|
|
assert result == []
|
|
|
|
|
|
class TestGetColumnCount:
|
|
"""Test column counting."""
|
|
|
|
def test_empty_table(self):
|
|
table = Table()
|
|
assert get_column_count(table) == 0
|
|
|
|
def test_table_with_header(self):
|
|
from pyWebLayout.abstract.block import TableRow, TableCell, Paragraph, Word
|
|
from pyWebLayout.style import Font
|
|
|
|
table = Table()
|
|
font = Font(font_size=12)
|
|
|
|
row = TableRow()
|
|
for text in ["A", "B", "C"]:
|
|
cell = TableCell(is_header=True)
|
|
para = Paragraph(font)
|
|
para.add_word(Word(text, font))
|
|
cell.add_block(para)
|
|
row.add_cell(cell)
|
|
|
|
table.add_row(row, section="header")
|
|
|
|
assert get_column_count(table) == 3
|
|
|
|
def test_table_with_body(self):
|
|
from pyWebLayout.abstract.block import TableRow, TableCell, Paragraph, Word
|
|
from pyWebLayout.style import Font
|
|
|
|
table = Table()
|
|
font = Font(font_size=12)
|
|
|
|
row = TableRow()
|
|
for text in ["1", "2"]:
|
|
cell = TableCell()
|
|
para = Paragraph(font)
|
|
para.add_word(Word(text, font))
|
|
cell.add_block(para)
|
|
row.add_cell(cell)
|
|
|
|
table.add_row(row, section="body")
|
|
|
|
assert get_column_count(table) == 2
|
|
|
|
|
|
class TestSampleTableRows:
|
|
"""Test row sampling."""
|
|
|
|
def test_sample_small_table(self):
|
|
from pyWebLayout.abstract.block import TableRow, TableCell, Paragraph
|
|
from pyWebLayout.abstract.inline import Word
|
|
from pyWebLayout.style import Font
|
|
|
|
table = Table()
|
|
font = Font(font_size=12)
|
|
|
|
for text in ["1", "2"]:
|
|
row = TableRow()
|
|
cell = TableCell()
|
|
para = Paragraph(font)
|
|
para.add_word(Word(text, font))
|
|
cell.add_block(para)
|
|
row.add_cell(cell)
|
|
table.add_row(row, section="body")
|
|
|
|
sampled = sample_table_rows(table, sample_size=5)
|
|
|
|
# Should get all rows (only 2)
|
|
assert len(sampled) == 2
|
|
|
|
def test_sample_large_table(self):
|
|
from pyWebLayout.abstract.block import TableRow, TableCell, Paragraph
|
|
from pyWebLayout.abstract.inline import Word
|
|
from pyWebLayout.style import Font
|
|
|
|
table = Table()
|
|
font = Font(font_size=12)
|
|
|
|
for i in range(20):
|
|
row = TableRow()
|
|
cell = TableCell()
|
|
para = Paragraph(font)
|
|
para.add_word(Word(str(i), font))
|
|
cell.add_block(para)
|
|
row.add_cell(cell)
|
|
table.add_row(row, section="body")
|
|
|
|
sampled = sample_table_rows(table, sample_size=5)
|
|
|
|
# Should get only 5 body rows
|
|
assert len(sampled) == 5
|
|
|
|
def test_sample_with_header_body_footer(self):
|
|
from pyWebLayout.abstract.block import TableRow, TableCell, Paragraph
|
|
from pyWebLayout.abstract.inline import Word
|
|
from pyWebLayout.style import Font
|
|
|
|
table = Table()
|
|
font = Font(font_size=12)
|
|
|
|
# 3 header rows
|
|
for i in range(3):
|
|
row = TableRow()
|
|
cell = TableCell(is_header=True)
|
|
para = Paragraph(font)
|
|
para.add_word(Word(f"H{i}", font))
|
|
cell.add_block(para)
|
|
row.add_cell(cell)
|
|
table.add_row(row, section="header")
|
|
|
|
# 10 body rows
|
|
for i in range(10):
|
|
row = TableRow()
|
|
cell = TableCell()
|
|
para = Paragraph(font)
|
|
para.add_word(Word(f"B{i}", font))
|
|
cell.add_block(para)
|
|
row.add_cell(cell)
|
|
table.add_row(row, section="body")
|
|
|
|
# 2 footer rows
|
|
for i in range(2):
|
|
row = TableRow()
|
|
cell = TableCell()
|
|
para = Paragraph(font)
|
|
para.add_word(Word(f"F{i}", font))
|
|
cell.add_block(para)
|
|
row.add_cell(cell)
|
|
table.add_row(row, section="footer")
|
|
|
|
sampled = sample_table_rows(table, sample_size=2)
|
|
|
|
# Should get 2 from each section = 6 total
|
|
assert len(sampled) == 6
|
|
|
|
|
|
class TestExtractHtmlColumnWidths:
|
|
"""Test HTML width extraction."""
|
|
|
|
def test_no_widths(self):
|
|
from pyWebLayout.abstract.block import TableRow, TableCell, Paragraph
|
|
from pyWebLayout.abstract.inline import Word
|
|
from pyWebLayout.style import Font
|
|
|
|
table = Table()
|
|
font = Font(font_size=12)
|
|
|
|
row = TableRow()
|
|
for text in ["A", "B"]:
|
|
cell = TableCell()
|
|
para = Paragraph(font)
|
|
para.add_word(Word(text, font))
|
|
cell.add_block(para)
|
|
row.add_cell(cell)
|
|
table.add_row(row, section="body")
|
|
|
|
widths = extract_html_column_widths(table)
|
|
|
|
assert widths == [None, None]
|
|
|
|
def test_cell_width_attributes(self):
|
|
from pyWebLayout.abstract.block import TableRow, TableCell, Paragraph
|
|
from pyWebLayout.abstract.inline import Word
|
|
from pyWebLayout.style import Font
|
|
|
|
table = Table()
|
|
font = Font(font_size=12)
|
|
|
|
row = TableRow()
|
|
|
|
cell1 = TableCell()
|
|
cell1.width = "100px"
|
|
para1 = Paragraph(font)
|
|
para1.add_word(Word("A", font))
|
|
cell1.add_block(para1)
|
|
row.add_cell(cell1)
|
|
|
|
cell2 = TableCell()
|
|
cell2.width = "150"
|
|
para2 = Paragraph(font)
|
|
para2.add_word(Word("B", font))
|
|
cell2.add_block(para2)
|
|
row.add_cell(cell2)
|
|
|
|
table.add_row(row, section="body")
|
|
|
|
widths = extract_html_column_widths(table)
|
|
|
|
assert widths == [100, 150]
|
|
|
|
|
|
class TestCalculateTableOverhead:
|
|
"""Test table overhead calculation."""
|
|
|
|
def test_basic_overhead(self):
|
|
style = TableStyle(border_width=1, cell_spacing=0)
|
|
overhead = calculate_table_overhead(3, style)
|
|
|
|
# 3 columns = 4 borders (n+1)
|
|
assert overhead == 4
|
|
|
|
def test_with_cell_spacing(self):
|
|
style = TableStyle(border_width=1, cell_spacing=5)
|
|
overhead = calculate_table_overhead(3, style)
|
|
|
|
# 4 borders + 2 spacings (n-1)
|
|
assert overhead == 4 + 10
|
|
|
|
def test_thicker_borders(self):
|
|
style = TableStyle(border_width=3, cell_spacing=0)
|
|
overhead = calculate_table_overhead(2, style)
|
|
|
|
# 2 columns = 3 borders * 3px
|
|
assert overhead == 9
|
|
|
|
|
|
class TestOptimizeTableLayout:
|
|
"""Test full table optimization."""
|
|
|
|
def test_optimize_simple_table(self):
|
|
from pyWebLayout.abstract.block import TableRow, TableCell, Paragraph
|
|
from pyWebLayout.abstract.inline import Word
|
|
from pyWebLayout.style import Font
|
|
|
|
table = Table()
|
|
font = Font(font_size=12)
|
|
|
|
row = TableRow()
|
|
for text in ["Short", "A bit longer text"]:
|
|
cell = TableCell()
|
|
para = Paragraph(font)
|
|
for word in text.split():
|
|
para.add_word(Word(word, font))
|
|
cell.add_block(para)
|
|
row.add_cell(cell)
|
|
table.add_row(row, section="body")
|
|
|
|
style = TableStyle()
|
|
widths = optimize_table_layout(table, available_width=400, style=style)
|
|
|
|
# Should return 2 column widths
|
|
assert len(widths) == 2
|
|
# Second column should be wider
|
|
assert widths[1] > widths[0]
|
|
|
|
def test_optimize_empty_table(self):
|
|
table = Table()
|
|
|
|
widths = optimize_table_layout(table, available_width=400)
|
|
|
|
assert widths == []
|
|
|
|
def test_optimize_respects_sample_size(self):
|
|
from pyWebLayout.abstract.block import TableRow, TableCell, Paragraph
|
|
from pyWebLayout.abstract.inline import Word
|
|
from pyWebLayout.style import Font
|
|
|
|
table = Table()
|
|
font = Font(font_size=12)
|
|
|
|
# Create 20 rows but only first 5 should be sampled
|
|
for i in range(20):
|
|
row = TableRow()
|
|
cell = TableCell()
|
|
para = Paragraph(font)
|
|
para.add_word(Word(f"Data {i}", font))
|
|
cell.add_block(para)
|
|
row.add_cell(cell)
|
|
table.add_row(row, section="body")
|
|
|
|
widths = optimize_table_layout(table, available_width=400, sample_size=5)
|
|
|
|
# Should return width for 1 column
|
|
assert len(widths) == 1
|
|
|
|
|
|
if __name__ == '__main__':
|
|
pytest.main([__file__, '-v'])
|