601 lines
19 KiB
Python
601 lines
19 KiB
Python
"""
|
|
Tests for table rendering components.
|
|
|
|
This module tests:
|
|
- TableStyle: Styling configuration for tables
|
|
- TableCellRenderer: Individual cell rendering
|
|
- TableRowRenderer: Row rendering with multiple cells
|
|
- TableRenderer: Complete table rendering
|
|
"""
|
|
|
|
import pytest
|
|
from PIL import Image, ImageDraw
|
|
from pyWebLayout.concrete.table import (
|
|
TableStyle,
|
|
TableCellRenderer,
|
|
TableRowRenderer,
|
|
TableRenderer
|
|
)
|
|
from pyWebLayout.abstract.block import (
|
|
Table, TableRow, TableCell, Paragraph, Heading, HeadingLevel,
|
|
Image as AbstractImage
|
|
)
|
|
from pyWebLayout.abstract.inline import Word
|
|
from pyWebLayout.style import Font
|
|
|
|
|
|
# ============================================================================
|
|
# Fixtures
|
|
# ============================================================================
|
|
|
|
@pytest.fixture
|
|
def sample_font():
|
|
"""Create a standard font for testing."""
|
|
return Font(font_size=12, colour=(0, 0, 0))
|
|
|
|
|
|
@pytest.fixture
|
|
def sample_canvas():
|
|
"""Create a PIL canvas for rendering."""
|
|
return Image.new('RGB', (800, 600), color=(255, 255, 255))
|
|
|
|
|
|
@pytest.fixture
|
|
def sample_draw(sample_canvas):
|
|
"""Create a PIL ImageDraw object."""
|
|
return ImageDraw.Draw(sample_canvas)
|
|
|
|
|
|
@pytest.fixture
|
|
def default_table_style():
|
|
"""Create default table style."""
|
|
return TableStyle()
|
|
|
|
|
|
@pytest.fixture
|
|
def custom_table_style():
|
|
"""Create custom table style."""
|
|
return TableStyle(
|
|
border_width=2,
|
|
border_color=(100, 100, 100),
|
|
cell_padding=(10, 10, 10, 10),
|
|
header_bg_color=(200, 200, 200),
|
|
cell_bg_color=(250, 250, 250),
|
|
alternate_row_color=(240, 240, 240),
|
|
cell_spacing=5
|
|
)
|
|
|
|
|
|
@pytest.fixture
|
|
def simple_table(sample_font):
|
|
"""Create a simple table with header and body."""
|
|
table = Table()
|
|
table.caption = "Test Table"
|
|
|
|
# Header row
|
|
header_row = TableRow()
|
|
header_cell1 = TableCell(is_header=True)
|
|
header_p1 = Paragraph(sample_font)
|
|
header_p1.add_word(Word("Column", sample_font))
|
|
header_p1.add_word(Word("1", sample_font))
|
|
header_cell1.add_block(header_p1)
|
|
|
|
header_cell2 = TableCell(is_header=True)
|
|
header_p2 = Paragraph(sample_font)
|
|
header_p2.add_word(Word("Column", sample_font))
|
|
header_p2.add_word(Word("2", sample_font))
|
|
header_cell2.add_block(header_p2)
|
|
|
|
header_row.add_cell(header_cell1)
|
|
header_row.add_cell(header_cell2)
|
|
table.add_row(header_row, section="header")
|
|
|
|
# Body row
|
|
body_row = TableRow()
|
|
body_cell1 = TableCell()
|
|
body_p1 = Paragraph(sample_font)
|
|
body_p1.add_word(Word("Data", sample_font))
|
|
body_p1.add_word(Word("1", sample_font))
|
|
body_cell1.add_block(body_p1)
|
|
|
|
body_cell2 = TableCell()
|
|
body_p2 = Paragraph(sample_font)
|
|
body_p2.add_word(Word("Data", sample_font))
|
|
body_p2.add_word(Word("2", sample_font))
|
|
body_cell2.add_block(body_p2)
|
|
|
|
body_row.add_cell(body_cell1)
|
|
body_row.add_cell(body_cell2)
|
|
table.add_row(body_row, section="body")
|
|
|
|
return table
|
|
|
|
|
|
# ============================================================================
|
|
# TableStyle Tests
|
|
# ============================================================================
|
|
|
|
class TestTableStyle:
|
|
"""Tests for TableStyle dataclass."""
|
|
|
|
def test_default_initialization(self):
|
|
"""Test TableStyle with default values."""
|
|
style = TableStyle()
|
|
|
|
assert style.border_width == 1
|
|
assert style.border_color == (0, 0, 0)
|
|
assert style.cell_padding == (5, 5, 5, 5)
|
|
assert style.header_bg_color == (240, 240, 240)
|
|
assert style.header_text_bold is True
|
|
assert style.cell_bg_color == (255, 255, 255)
|
|
assert style.alternate_row_color == (250, 250, 250)
|
|
assert style.cell_spacing == 0
|
|
|
|
def test_custom_initialization(self):
|
|
"""Test TableStyle with custom values."""
|
|
style = TableStyle(
|
|
border_width=3,
|
|
border_color=(255, 0, 0),
|
|
cell_padding=(10, 15, 20, 25),
|
|
header_bg_color=(100, 100, 100),
|
|
header_text_bold=False,
|
|
cell_bg_color=(200, 200, 200),
|
|
alternate_row_color=None,
|
|
cell_spacing=10
|
|
)
|
|
|
|
assert style.border_width == 3
|
|
assert style.border_color == (255, 0, 0)
|
|
assert style.cell_padding == (10, 15, 20, 25)
|
|
assert style.header_bg_color == (100, 100, 100)
|
|
assert style.header_text_bold is False
|
|
assert style.cell_bg_color == (200, 200, 200)
|
|
assert style.alternate_row_color is None
|
|
assert style.cell_spacing == 10
|
|
|
|
def test_all_attributes_accessible(self, custom_table_style):
|
|
"""Test that all style attributes are accessible."""
|
|
# Verify all attributes exist and are accessible
|
|
assert hasattr(custom_table_style, 'border_width')
|
|
assert hasattr(custom_table_style, 'border_color')
|
|
assert hasattr(custom_table_style, 'cell_padding')
|
|
assert hasattr(custom_table_style, 'header_bg_color')
|
|
assert hasattr(custom_table_style, 'header_text_bold')
|
|
assert hasattr(custom_table_style, 'cell_bg_color')
|
|
assert hasattr(custom_table_style, 'alternate_row_color')
|
|
assert hasattr(custom_table_style, 'cell_spacing')
|
|
|
|
|
|
# ============================================================================
|
|
# TableCellRenderer Tests
|
|
# ============================================================================
|
|
|
|
class TestTableCellRenderer:
|
|
"""Tests for TableCellRenderer."""
|
|
|
|
def test_initialization(self, sample_font, sample_draw, default_table_style):
|
|
"""Test TableCellRenderer initialization."""
|
|
cell = TableCell()
|
|
cell_renderer = TableCellRenderer(
|
|
cell,
|
|
origin=(10, 10),
|
|
size=(100, 50),
|
|
draw=sample_draw,
|
|
style=default_table_style
|
|
)
|
|
|
|
assert cell_renderer._cell == cell
|
|
# Origin and size may be numpy arrays, so compare values
|
|
import numpy as np
|
|
assert np.array_equal(cell_renderer._origin, (10, 10))
|
|
assert np.array_equal(cell_renderer._size, (100, 50))
|
|
assert cell_renderer._draw == sample_draw
|
|
assert cell_renderer._style == default_table_style
|
|
assert cell_renderer._is_header_section is False
|
|
|
|
def test_initialization_with_header(self, sample_font, sample_draw, default_table_style):
|
|
"""Test TableCellRenderer initialization for header cell."""
|
|
cell = TableCell(is_header=True)
|
|
cell_renderer = TableCellRenderer(
|
|
cell,
|
|
origin=(10, 10),
|
|
size=(100, 50),
|
|
draw=sample_draw,
|
|
style=default_table_style,
|
|
is_header_section=True
|
|
)
|
|
|
|
assert cell_renderer._is_header_section is True
|
|
|
|
def test_render_empty_cell(self, sample_font, sample_draw, sample_canvas, default_table_style):
|
|
"""Test rendering an empty cell."""
|
|
cell = TableCell()
|
|
cell_renderer = TableCellRenderer(
|
|
cell,
|
|
origin=(10, 10),
|
|
size=(100, 50),
|
|
draw=sample_draw,
|
|
style=default_table_style,
|
|
canvas=sample_canvas
|
|
)
|
|
|
|
result = cell_renderer.render()
|
|
# Render returns None (draws directly on canvas)
|
|
assert result is None
|
|
|
|
def test_render_cell_with_text(self, sample_font, sample_draw, sample_canvas, default_table_style):
|
|
"""Test rendering a cell with text content."""
|
|
cell = TableCell()
|
|
paragraph = Paragraph(sample_font)
|
|
paragraph.add_word(Word("Test", sample_font))
|
|
paragraph.add_word(Word("Content", sample_font))
|
|
cell.add_block(paragraph)
|
|
|
|
cell_renderer = TableCellRenderer(
|
|
cell,
|
|
origin=(10, 10),
|
|
size=(200, 50),
|
|
draw=sample_draw,
|
|
style=default_table_style,
|
|
canvas=sample_canvas
|
|
)
|
|
|
|
result = cell_renderer.render()
|
|
assert result is None
|
|
|
|
def test_render_header_cell(self, sample_font, sample_draw, sample_canvas, default_table_style):
|
|
"""Test rendering a header cell with different styling."""
|
|
cell = TableCell(is_header=True)
|
|
paragraph = Paragraph(sample_font)
|
|
paragraph.add_word(Word("Header", sample_font))
|
|
cell.add_block(paragraph)
|
|
|
|
cell_renderer = TableCellRenderer(
|
|
cell,
|
|
origin=(10, 10),
|
|
size=(200, 50),
|
|
draw=sample_draw,
|
|
style=default_table_style,
|
|
is_header_section=True,
|
|
canvas=sample_canvas
|
|
)
|
|
|
|
result = cell_renderer.render()
|
|
assert result is None
|
|
|
|
def test_render_cell_with_heading(self, sample_font, sample_draw, sample_canvas, default_table_style):
|
|
"""Test rendering a cell with heading content."""
|
|
cell = TableCell()
|
|
heading = Heading(HeadingLevel.H2, sample_font)
|
|
heading.add_word(Word("Heading", sample_font))
|
|
cell.add_block(heading)
|
|
|
|
cell_renderer = TableCellRenderer(
|
|
cell,
|
|
origin=(10, 10),
|
|
size=(200, 50),
|
|
draw=sample_draw,
|
|
style=default_table_style,
|
|
canvas=sample_canvas
|
|
)
|
|
|
|
result = cell_renderer.render()
|
|
assert result is None
|
|
|
|
def test_in_object(self, sample_font, sample_draw, default_table_style):
|
|
"""Test in_object method for point detection."""
|
|
cell = TableCell()
|
|
cell_renderer = TableCellRenderer(
|
|
cell,
|
|
origin=(10, 10),
|
|
size=(100, 50),
|
|
draw=sample_draw,
|
|
style=default_table_style
|
|
)
|
|
|
|
# Point inside cell
|
|
assert cell_renderer.in_object((50, 30)) == True
|
|
|
|
# Point outside cell
|
|
assert cell_renderer.in_object((150, 30)) == False
|
|
assert cell_renderer.in_object((50, 100)) == False
|
|
|
|
def test_properties_access(self, sample_font, sample_draw, default_table_style):
|
|
"""Test accessing cell renderer properties."""
|
|
import numpy as np
|
|
cell = TableCell()
|
|
cell_renderer = TableCellRenderer(
|
|
cell,
|
|
origin=(10, 10),
|
|
size=(100, 50),
|
|
draw=sample_draw,
|
|
style=default_table_style
|
|
)
|
|
|
|
# May be numpy arrays
|
|
assert np.array_equal(cell_renderer._origin, (10, 10))
|
|
assert np.array_equal(cell_renderer._size, (100, 50))
|
|
|
|
|
|
# ============================================================================
|
|
# TableRowRenderer Tests
|
|
# ============================================================================
|
|
|
|
class TestTableRowRenderer:
|
|
"""Tests for TableRowRenderer."""
|
|
|
|
def test_initialization(self, sample_font, sample_draw, default_table_style):
|
|
"""Test TableRowRenderer initialization."""
|
|
row = TableRow()
|
|
column_widths = [100, 150, 200]
|
|
|
|
row_renderer = TableRowRenderer(
|
|
row,
|
|
origin=(10, 10),
|
|
column_widths=column_widths,
|
|
row_height=50,
|
|
draw=sample_draw,
|
|
style=default_table_style
|
|
)
|
|
|
|
assert row_renderer._row == row
|
|
assert row_renderer._column_widths == column_widths
|
|
assert row_renderer._row_height == 50
|
|
assert row_renderer._draw == sample_draw
|
|
assert row_renderer._style == default_table_style
|
|
assert row_renderer._is_header_section is False
|
|
|
|
def test_render_empty_row(self, sample_font, sample_draw, default_table_style):
|
|
"""Test rendering an empty row."""
|
|
row = TableRow()
|
|
|
|
row_renderer = TableRowRenderer(
|
|
row,
|
|
origin=(10, 10),
|
|
column_widths=[100, 100],
|
|
row_height=50,
|
|
draw=sample_draw,
|
|
style=default_table_style
|
|
)
|
|
|
|
result = row_renderer.render()
|
|
assert result is None
|
|
|
|
def test_render_row_with_cells(self, sample_font, sample_draw, sample_canvas, default_table_style):
|
|
"""Test rendering a row with multiple cells."""
|
|
row = TableRow()
|
|
|
|
# Add cells to row
|
|
for i in range(3):
|
|
cell = TableCell()
|
|
paragraph = Paragraph(sample_font)
|
|
paragraph.add_word(Word(f"Cell{i}", sample_font))
|
|
cell.add_block(paragraph)
|
|
row.add_cell(cell)
|
|
|
|
row_renderer = TableRowRenderer(
|
|
row,
|
|
origin=(10, 10),
|
|
column_widths=[100, 100, 100],
|
|
row_height=50,
|
|
draw=sample_draw,
|
|
style=default_table_style,
|
|
canvas=sample_canvas
|
|
)
|
|
|
|
result = row_renderer.render()
|
|
assert result is None
|
|
# Verify cells were created
|
|
assert len(row_renderer._cell_renderers) == 3
|
|
|
|
def test_render_row_with_colspan(self, sample_font, sample_draw, sample_canvas, default_table_style):
|
|
"""Test rendering a row with cells that span multiple columns."""
|
|
row = TableRow()
|
|
|
|
# Cell with colspan=2
|
|
cell1 = TableCell(colspan=2)
|
|
paragraph1 = Paragraph(sample_font)
|
|
paragraph1.add_word(Word("Spanning", sample_font))
|
|
cell1.add_block(paragraph1)
|
|
row.add_cell(cell1)
|
|
|
|
# Normal cell
|
|
cell2 = TableCell()
|
|
paragraph2 = Paragraph(sample_font)
|
|
paragraph2.add_word(Word("Normal", sample_font))
|
|
cell2.add_block(paragraph2)
|
|
row.add_cell(cell2)
|
|
|
|
row_renderer = TableRowRenderer(
|
|
row,
|
|
origin=(10, 10),
|
|
column_widths=[100, 100, 100],
|
|
row_height=50,
|
|
draw=sample_draw,
|
|
style=default_table_style,
|
|
canvas=sample_canvas
|
|
)
|
|
|
|
result = row_renderer.render()
|
|
assert result is None
|
|
|
|
|
|
# ============================================================================
|
|
# TableRenderer Tests
|
|
# ============================================================================
|
|
|
|
class TestTableRenderer:
|
|
"""Tests for TableRenderer."""
|
|
|
|
def test_initialization(self, simple_table, sample_draw, default_table_style):
|
|
"""Test TableRenderer initialization."""
|
|
import numpy as np
|
|
table_renderer = TableRenderer(
|
|
simple_table,
|
|
origin=(10, 10),
|
|
available_width=600,
|
|
draw=sample_draw,
|
|
style=default_table_style
|
|
)
|
|
|
|
assert table_renderer._table == simple_table
|
|
assert np.array_equal(table_renderer._origin, (10, 10))
|
|
assert table_renderer._available_width == 600
|
|
assert table_renderer._draw == sample_draw
|
|
assert table_renderer._style == default_table_style
|
|
|
|
def test_dimension_calculation(self, simple_table, sample_draw, default_table_style):
|
|
"""Test table dimension calculation."""
|
|
table_renderer = TableRenderer(
|
|
simple_table,
|
|
origin=(10, 10),
|
|
available_width=600,
|
|
draw=sample_draw,
|
|
style=default_table_style
|
|
)
|
|
|
|
# Check that dimensions were calculated
|
|
assert len(table_renderer._column_widths) == 2
|
|
assert len(table_renderer._row_heights) == 3 # header, body, footer
|
|
assert all(width > 0 for width in table_renderer._column_widths)
|
|
|
|
def test_render_simple_table(self, simple_table, sample_draw, sample_canvas, default_table_style):
|
|
"""Test rendering a complete simple table."""
|
|
table_renderer = TableRenderer(
|
|
simple_table,
|
|
origin=(10, 10),
|
|
available_width=600,
|
|
draw=sample_draw,
|
|
style=default_table_style,
|
|
canvas=sample_canvas
|
|
)
|
|
|
|
result = table_renderer.render()
|
|
assert result is None
|
|
# Verify rows were created
|
|
assert len(table_renderer._row_renderers) == 2 # 1 header + 1 body
|
|
|
|
def test_render_table_with_caption(self, simple_table, sample_draw, sample_canvas, default_table_style):
|
|
"""Test rendering a table with caption."""
|
|
simple_table.caption = "Test Table Caption"
|
|
|
|
table_renderer = TableRenderer(
|
|
simple_table,
|
|
origin=(10, 10),
|
|
available_width=600,
|
|
draw=sample_draw,
|
|
style=default_table_style,
|
|
canvas=sample_canvas
|
|
)
|
|
|
|
result = table_renderer.render()
|
|
assert result is None
|
|
|
|
def test_height_property(self, simple_table, sample_draw, default_table_style):
|
|
"""Test table height property."""
|
|
table_renderer = TableRenderer(
|
|
simple_table,
|
|
origin=(10, 10),
|
|
available_width=600,
|
|
draw=sample_draw,
|
|
style=default_table_style
|
|
)
|
|
|
|
height = table_renderer.height
|
|
assert isinstance(height, int)
|
|
assert height > 0
|
|
|
|
def test_width_property(self, simple_table, sample_draw, default_table_style):
|
|
"""Test table width property."""
|
|
table_renderer = TableRenderer(
|
|
simple_table,
|
|
origin=(10, 10),
|
|
available_width=600,
|
|
draw=sample_draw,
|
|
style=default_table_style
|
|
)
|
|
|
|
width = table_renderer.width
|
|
assert isinstance(width, int)
|
|
assert width > 0
|
|
assert width <= 600 # Should not exceed available width
|
|
|
|
def test_empty_table(self, sample_draw, default_table_style):
|
|
"""Test rendering an empty table."""
|
|
empty_table = Table()
|
|
|
|
table_renderer = TableRenderer(
|
|
empty_table,
|
|
origin=(10, 10),
|
|
available_width=600,
|
|
draw=sample_draw,
|
|
style=default_table_style
|
|
)
|
|
|
|
# Should handle gracefully
|
|
assert table_renderer is not None
|
|
|
|
def test_table_with_footer(self, sample_font, sample_draw, sample_canvas, default_table_style):
|
|
"""Test rendering a table with footer rows."""
|
|
table = Table()
|
|
|
|
# Add header
|
|
header_row = TableRow()
|
|
header_cell = TableCell(is_header=True)
|
|
header_p = Paragraph(sample_font)
|
|
header_p.add_word(Word("Header", sample_font))
|
|
header_cell.add_block(header_p)
|
|
header_row.add_cell(header_cell)
|
|
table.add_row(header_row, section="header")
|
|
|
|
# Add body
|
|
body_row = TableRow()
|
|
body_cell = TableCell()
|
|
body_p = Paragraph(sample_font)
|
|
body_p.add_word(Word("Body", sample_font))
|
|
body_cell.add_block(body_p)
|
|
body_row.add_cell(body_cell)
|
|
table.add_row(body_row, section="body")
|
|
|
|
# Add footer
|
|
footer_row = TableRow()
|
|
footer_cell = TableCell()
|
|
footer_p = Paragraph(sample_font)
|
|
footer_p.add_word(Word("Footer", sample_font))
|
|
footer_cell.add_block(footer_p)
|
|
footer_row.add_cell(footer_cell)
|
|
table.add_row(footer_row, section="footer")
|
|
|
|
table_renderer = TableRenderer(
|
|
table,
|
|
origin=(10, 10),
|
|
available_width=600,
|
|
draw=sample_draw,
|
|
style=default_table_style,
|
|
canvas=sample_canvas
|
|
)
|
|
|
|
result = table_renderer.render()
|
|
assert result is None
|
|
assert len(table_renderer._row_renderers) == 3 # header + body + footer
|
|
|
|
def test_in_object(self, simple_table, sample_draw, default_table_style):
|
|
"""Test in_object method for table."""
|
|
table_renderer = TableRenderer(
|
|
simple_table,
|
|
origin=(10, 10),
|
|
available_width=600,
|
|
draw=sample_draw,
|
|
style=default_table_style
|
|
)
|
|
|
|
# Point inside table
|
|
assert table_renderer.in_object((50, 50)) == True
|
|
|
|
# Point outside table
|
|
assert table_renderer.in_object((1000, 1000)) == False
|
|
|
|
|
|
if __name__ == "__main__":
|
|
pytest.main([__file__, "-v"])
|