""" 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 ) 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)) # Point outside cell assert not cell_renderer.in_object((150, 30)) assert not cell_renderer.in_object((50, 100)) 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)) # Point outside table assert not table_renderer.in_object((1000, 1000)) if __name__ == "__main__": pytest.main([__file__, "-v"])