""" 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'])