757 lines
32 KiB
Python
757 lines
32 KiB
Python
"""
|
|
Unit tests for pyWebLayout.concrete.page module.
|
|
Tests the Container and Page classes for layout and rendering functionality.
|
|
"""
|
|
|
|
import unittest
|
|
import numpy as np
|
|
from PIL import Image
|
|
from unittest.mock import Mock, patch, MagicMock
|
|
|
|
from pyWebLayout.concrete.page import Container, Page
|
|
from pyWebLayout.concrete.box import Box
|
|
from pyWebLayout.style.layout import Alignment
|
|
|
|
|
|
class TestContainer(unittest.TestCase):
|
|
"""Test cases for the Container class"""
|
|
|
|
def setUp(self):
|
|
"""Set up test fixtures"""
|
|
self.origin = (0, 0)
|
|
self.size = (400, 300)
|
|
self.callback = Mock()
|
|
|
|
# Create mock child elements
|
|
self.mock_child1 = Mock()
|
|
self.mock_child1._size = np.array([100, 50])
|
|
self.mock_child1._origin = np.array([0, 0])
|
|
self.mock_child1.render.return_value = Image.new('RGBA', (100, 50), (255, 0, 0, 255))
|
|
|
|
self.mock_child2 = Mock()
|
|
self.mock_child2._size = np.array([120, 60])
|
|
self.mock_child2._origin = np.array([0, 0])
|
|
self.mock_child2.render.return_value = Image.new('RGBA', (120, 60), (0, 255, 0, 255))
|
|
|
|
def test_container_initialization_basic(self):
|
|
"""Test basic container initialization"""
|
|
container = Container(self.origin, self.size)
|
|
|
|
np.testing.assert_array_equal(container._origin, np.array(self.origin))
|
|
np.testing.assert_array_equal(container._size, np.array(self.size))
|
|
self.assertEqual(container._direction, 'vertical')
|
|
self.assertEqual(container._spacing, 5)
|
|
self.assertEqual(len(container._children), 0)
|
|
self.assertEqual(container._padding, (10, 10, 10, 10))
|
|
self.assertEqual(container._halign, Alignment.CENTER)
|
|
self.assertEqual(container._valign, Alignment.CENTER)
|
|
|
|
def test_container_initialization_with_params(self):
|
|
"""Test container initialization with custom parameters"""
|
|
custom_direction = 'horizontal'
|
|
custom_spacing = 15
|
|
custom_padding = (5, 8, 5, 8)
|
|
|
|
container = Container(
|
|
self.origin, self.size,
|
|
direction=custom_direction,
|
|
spacing=custom_spacing,
|
|
callback=self.callback,
|
|
halign=Alignment.LEFT,
|
|
valign=Alignment.TOP,
|
|
padding=custom_padding
|
|
)
|
|
|
|
self.assertEqual(container._direction, custom_direction)
|
|
self.assertEqual(container._spacing, custom_spacing)
|
|
self.assertEqual(container._callback, self.callback)
|
|
self.assertEqual(container._halign, Alignment.LEFT)
|
|
self.assertEqual(container._valign, Alignment.TOP)
|
|
self.assertEqual(container._padding, custom_padding)
|
|
|
|
def test_add_child(self):
|
|
"""Test adding child elements"""
|
|
container = Container(self.origin, self.size)
|
|
|
|
result = container.add_child(self.mock_child1)
|
|
|
|
self.assertEqual(len(container._children), 1)
|
|
self.assertEqual(container._children[0], self.mock_child1)
|
|
self.assertEqual(result, container) # Should return self for chaining
|
|
|
|
def test_add_multiple_children(self):
|
|
"""Test adding multiple child elements"""
|
|
container = Container(self.origin, self.size)
|
|
|
|
container.add_child(self.mock_child1)
|
|
container.add_child(self.mock_child2)
|
|
|
|
self.assertEqual(len(container._children), 2)
|
|
self.assertEqual(container._children[0], self.mock_child1)
|
|
self.assertEqual(container._children[1], self.mock_child2)
|
|
|
|
def test_layout_vertical_centered(self):
|
|
"""Test vertical layout with center alignment"""
|
|
container = Container(self.origin, self.size, direction='vertical', halign=Alignment.CENTER)
|
|
container.add_child(self.mock_child1)
|
|
container.add_child(self.mock_child2)
|
|
|
|
container.layout()
|
|
|
|
# Check that children have been positioned
|
|
# First child should be at top padding
|
|
expected_x1 = 10 + (380 - 100) // 2 # padding + centered in available width
|
|
expected_y1 = 10 # top padding
|
|
np.testing.assert_array_equal(self.mock_child1._origin, np.array([expected_x1, expected_y1]))
|
|
|
|
# Second child should be below first child + spacing
|
|
expected_x2 = 10 + (380 - 120) // 2 # padding + centered in available width
|
|
expected_y2 = 10 + 50 + 5 # top padding + first child height + spacing
|
|
np.testing.assert_array_equal(self.mock_child2._origin, np.array([expected_x2, expected_y2]))
|
|
|
|
def test_layout_vertical_left_aligned(self):
|
|
"""Test vertical layout with left alignment"""
|
|
container = Container(self.origin, self.size, direction='vertical', halign=Alignment.LEFT)
|
|
container.add_child(self.mock_child1)
|
|
container.add_child(self.mock_child2)
|
|
|
|
container.layout()
|
|
|
|
# Both children should be left-aligned
|
|
expected_x = 10 # left padding
|
|
np.testing.assert_array_equal(self.mock_child1._origin, np.array([expected_x, 10]))
|
|
np.testing.assert_array_equal(self.mock_child2._origin, np.array([expected_x, 65]))
|
|
|
|
def test_layout_vertical_right_aligned(self):
|
|
"""Test vertical layout with right alignment"""
|
|
container = Container(self.origin, self.size, direction='vertical', halign=Alignment.RIGHT)
|
|
container.add_child(self.mock_child1)
|
|
container.add_child(self.mock_child2)
|
|
|
|
container.layout()
|
|
|
|
# Children should be right-aligned
|
|
expected_x1 = 10 + 380 - 100 # left padding + available width - child width
|
|
expected_x2 = 10 + 380 - 120
|
|
np.testing.assert_array_equal(self.mock_child1._origin, np.array([expected_x1, 10]))
|
|
np.testing.assert_array_equal(self.mock_child2._origin, np.array([expected_x2, 65]))
|
|
|
|
def test_layout_horizontal_centered(self):
|
|
"""Test horizontal layout with center alignment"""
|
|
container = Container(self.origin, self.size, direction='horizontal', valign=Alignment.CENTER)
|
|
container.add_child(self.mock_child1)
|
|
container.add_child(self.mock_child2)
|
|
|
|
container.layout()
|
|
|
|
# Children should be positioned horizontally
|
|
expected_x1 = 10 # left padding
|
|
expected_x2 = 10 + 100 + 5 # left padding + first child width + spacing
|
|
|
|
# Vertically centered
|
|
expected_y1 = 10 + (280 - 50) // 2 # top padding + centered in available height
|
|
expected_y2 = 10 + (280 - 60) // 2
|
|
|
|
np.testing.assert_array_equal(self.mock_child1._origin, np.array([expected_x1, expected_y1]))
|
|
np.testing.assert_array_equal(self.mock_child2._origin, np.array([expected_x2, expected_y2]))
|
|
|
|
def test_layout_horizontal_top_aligned(self):
|
|
"""Test horizontal layout with top alignment"""
|
|
container = Container(self.origin, self.size, direction='horizontal', valign=Alignment.TOP)
|
|
container.add_child(self.mock_child1)
|
|
container.add_child(self.mock_child2)
|
|
|
|
container.layout()
|
|
|
|
# Both children should be top-aligned
|
|
expected_y = 10 # top padding
|
|
np.testing.assert_array_equal(self.mock_child1._origin, np.array([10, expected_y]))
|
|
np.testing.assert_array_equal(self.mock_child2._origin, np.array([115, expected_y]))
|
|
|
|
def test_layout_horizontal_bottom_aligned(self):
|
|
"""Test horizontal layout with bottom alignment"""
|
|
container = Container(self.origin, self.size, direction='horizontal', valign=Alignment.BOTTOM)
|
|
container.add_child(self.mock_child1)
|
|
container.add_child(self.mock_child2)
|
|
|
|
container.layout()
|
|
|
|
# Children should be bottom-aligned
|
|
expected_y1 = 10 + 280 - 50 # top padding + available height - child height
|
|
expected_y2 = 10 + 280 - 60
|
|
np.testing.assert_array_equal(self.mock_child1._origin, np.array([10, expected_y1]))
|
|
np.testing.assert_array_equal(self.mock_child2._origin, np.array([115, expected_y2]))
|
|
|
|
def test_layout_empty_container(self):
|
|
"""Test layout with no children"""
|
|
container = Container(self.origin, self.size)
|
|
|
|
# Should not raise an error
|
|
container.layout()
|
|
|
|
self.assertEqual(len(container._children), 0)
|
|
|
|
def test_layout_with_layoutable_children(self):
|
|
"""Test layout with children that are also layoutable"""
|
|
# Create a mock child that implements Layoutable
|
|
mock_layoutable_child = Mock()
|
|
mock_layoutable_child._size = np.array([80, 40])
|
|
mock_layoutable_child._origin = np.array([0, 0])
|
|
|
|
# Make it look like a Layoutable by adding layout method
|
|
from pyWebLayout.core.base import Layoutable
|
|
mock_layoutable_child.__class__ = type('MockLayoutable', (Mock, Layoutable), {})
|
|
mock_layoutable_child.layout = Mock()
|
|
|
|
container = Container(self.origin, self.size)
|
|
container.add_child(mock_layoutable_child)
|
|
|
|
container.layout()
|
|
|
|
# Child's layout method should have been called
|
|
mock_layoutable_child.layout.assert_called_once()
|
|
|
|
def test_render_empty_container(self):
|
|
"""Test rendering empty container"""
|
|
container = Container(self.origin, self.size)
|
|
result = container.render()
|
|
|
|
self.assertIsInstance(result, Image.Image)
|
|
self.assertEqual(result.size, tuple(self.size))
|
|
|
|
def test_render_with_children(self):
|
|
"""Test rendering container with children"""
|
|
container = Container(self.origin, self.size)
|
|
container.add_child(self.mock_child1)
|
|
container.add_child(self.mock_child2)
|
|
|
|
result = container.render()
|
|
|
|
self.assertIsInstance(result, Image.Image)
|
|
self.assertEqual(result.size, tuple(self.size))
|
|
|
|
# Children should have been rendered
|
|
self.mock_child1.render.assert_called_once()
|
|
self.mock_child2.render.assert_called_once()
|
|
|
|
def test_render_calls_layout(self):
|
|
"""Test that render calls layout"""
|
|
container = Container(self.origin, self.size)
|
|
container.add_child(self.mock_child1)
|
|
|
|
with patch.object(container, 'layout') as mock_layout:
|
|
result = container.render()
|
|
|
|
mock_layout.assert_called_once()
|
|
|
|
def test_custom_spacing(self):
|
|
"""Test container with custom spacing"""
|
|
custom_spacing = 20
|
|
container = Container(self.origin, self.size, spacing=custom_spacing)
|
|
container.add_child(self.mock_child1)
|
|
container.add_child(self.mock_child2)
|
|
|
|
container.layout()
|
|
|
|
# Second child should be positioned with custom spacing
|
|
expected_y2 = 10 + 50 + custom_spacing # top padding + first child height + custom spacing
|
|
self.assertEqual(self.mock_child2._origin[1], expected_y2)
|
|
|
|
|
|
class TestPage(unittest.TestCase):
|
|
"""Test cases for the Page class"""
|
|
|
|
def setUp(self):
|
|
"""Set up test fixtures"""
|
|
self.page_size = (800, 600)
|
|
self.background_color = (255, 255, 255)
|
|
|
|
# Create mock child elements
|
|
self.mock_child1 = Mock()
|
|
self.mock_child1._size = np.array([200, 100])
|
|
self.mock_child1._origin = np.array([0, 0])
|
|
self.mock_child1.render.return_value = Image.new('RGBA', (200, 100), (255, 0, 0, 255))
|
|
|
|
self.mock_child2 = Mock()
|
|
self.mock_child2._size = np.array([150, 80])
|
|
self.mock_child2._origin = np.array([0, 0])
|
|
self.mock_child2.render.return_value = Image.new('RGBA', (150, 80), (0, 255, 0, 255))
|
|
|
|
def test_page_initialization_basic(self):
|
|
"""Test basic page initialization"""
|
|
page = Page()
|
|
|
|
np.testing.assert_array_equal(page._origin, np.array([0, 0]))
|
|
np.testing.assert_array_equal(page._size, np.array([800, 600]))
|
|
self.assertEqual(page._background_color, (255, 255, 255))
|
|
self.assertEqual(page._mode, 'RGBA')
|
|
self.assertEqual(page._direction, 'vertical')
|
|
self.assertEqual(page._spacing, 10)
|
|
self.assertEqual(page._halign, Alignment.CENTER)
|
|
self.assertEqual(page._valign, Alignment.TOP)
|
|
|
|
def test_page_initialization_with_params(self):
|
|
"""Test page initialization with custom parameters"""
|
|
custom_size = (1024, 768)
|
|
custom_background = (240, 240, 240)
|
|
custom_mode = 'RGB'
|
|
|
|
page = Page(
|
|
size=custom_size,
|
|
background_color=custom_background,
|
|
mode=custom_mode
|
|
)
|
|
|
|
np.testing.assert_array_equal(page._size, np.array(custom_size))
|
|
self.assertEqual(page._background_color, custom_background)
|
|
self.assertEqual(page._mode, custom_mode)
|
|
|
|
def test_page_add_child(self):
|
|
"""Test adding child elements to page"""
|
|
page = Page()
|
|
|
|
page.add_child(self.mock_child1)
|
|
page.add_child(self.mock_child2)
|
|
|
|
self.assertEqual(len(page._children), 2)
|
|
self.assertEqual(page._children[0], self.mock_child1)
|
|
self.assertEqual(page._children[1], self.mock_child2)
|
|
|
|
def test_page_layout(self):
|
|
"""Test page layout functionality"""
|
|
page = Page()
|
|
page.add_child(self.mock_child1)
|
|
page.add_child(self.mock_child2)
|
|
|
|
page.layout()
|
|
|
|
# Children should be positioned vertically, centered horizontally
|
|
expected_x1 = (800 - 200) // 2 # Centered horizontally
|
|
expected_y1 = 10 # Top padding
|
|
np.testing.assert_array_equal(self.mock_child1._origin, np.array([expected_x1, expected_y1]))
|
|
|
|
expected_x2 = (800 - 150) // 2 # Centered horizontally
|
|
expected_y2 = 10 + 100 + 10 # Top padding + first child height + spacing
|
|
np.testing.assert_array_equal(self.mock_child2._origin, np.array([expected_x2, expected_y2]))
|
|
|
|
def test_page_render_empty(self):
|
|
"""Test rendering empty page"""
|
|
page = Page(size=self.page_size, background_color=self.background_color)
|
|
result = page.render()
|
|
|
|
self.assertIsInstance(result, Image.Image)
|
|
self.assertEqual(result.size, self.page_size)
|
|
self.assertEqual(result.mode, 'RGBA')
|
|
|
|
# Check that background color is applied
|
|
# Sample a pixel from the center to verify background
|
|
center_pixel = result.getpixel((400, 300))
|
|
self.assertEqual(center_pixel[:3], self.background_color)
|
|
|
|
def test_page_render_with_children_rgba(self):
|
|
"""Test rendering page with children (RGBA mode)"""
|
|
page = Page(size=self.page_size, background_color=self.background_color)
|
|
page.add_child(self.mock_child1)
|
|
page.add_child(self.mock_child2)
|
|
|
|
result = page.render()
|
|
|
|
self.assertIsInstance(result, Image.Image)
|
|
self.assertEqual(result.size, self.page_size)
|
|
self.assertEqual(result.mode, 'RGBA')
|
|
|
|
# Children should have been rendered
|
|
self.mock_child1.render.assert_called_once()
|
|
self.mock_child2.render.assert_called_once()
|
|
|
|
def test_page_render_with_children_rgb(self):
|
|
"""Test rendering page with children (RGB mode)"""
|
|
# Create children that return RGB images
|
|
rgb_child = Mock()
|
|
rgb_child._size = np.array([100, 50])
|
|
rgb_child._origin = np.array([0, 0])
|
|
rgb_child.render.return_value = Image.new('RGB', (100, 50), (255, 0, 0))
|
|
|
|
page = Page(size=self.page_size, background_color=self.background_color, mode='RGB')
|
|
page.add_child(rgb_child)
|
|
|
|
result = page.render()
|
|
|
|
self.assertIsInstance(result, Image.Image)
|
|
self.assertEqual(result.size, self.page_size)
|
|
self.assertEqual(result.mode, 'RGB')
|
|
|
|
rgb_child.render.assert_called_once()
|
|
|
|
def test_page_render_calls_layout(self):
|
|
"""Test that page render calls layout"""
|
|
page = Page()
|
|
page.add_child(self.mock_child1)
|
|
|
|
with patch.object(page, 'layout') as mock_layout:
|
|
result = page.render()
|
|
|
|
mock_layout.assert_called_once()
|
|
|
|
def test_page_inherits_container_functionality(self):
|
|
"""Test that Page inherits Container functionality"""
|
|
page = Page()
|
|
|
|
# Should inherit Container methods
|
|
self.assertTrue(hasattr(page, 'add_child'))
|
|
self.assertTrue(hasattr(page, 'layout'))
|
|
self.assertTrue(hasattr(page, '_children'))
|
|
self.assertTrue(hasattr(page, '_direction'))
|
|
self.assertTrue(hasattr(page, '_spacing'))
|
|
|
|
def test_page_with_mixed_child_image_modes(self):
|
|
"""Test page with children having different image modes"""
|
|
# Create children with different modes
|
|
rgba_child = Mock()
|
|
rgba_child._size = np.array([100, 50])
|
|
rgba_child._origin = np.array([0, 0])
|
|
rgba_child.render.return_value = Image.new('RGBA', (100, 50), (255, 0, 0, 255))
|
|
|
|
rgb_child = Mock()
|
|
rgb_child._size = np.array([100, 50])
|
|
rgb_child._origin = np.array([0, 0])
|
|
rgb_child.render.return_value = Image.new('RGB', (100, 50), (0, 255, 0))
|
|
|
|
page = Page()
|
|
page.add_child(rgba_child)
|
|
page.add_child(rgb_child)
|
|
|
|
result = page.render()
|
|
|
|
self.assertIsInstance(result, Image.Image)
|
|
self.assertEqual(result.mode, 'RGBA')
|
|
|
|
# Both children should have been rendered
|
|
rgba_child.render.assert_called_once()
|
|
rgb_child.render.assert_called_once()
|
|
|
|
def test_page_background_color_application(self):
|
|
"""Test that background color is properly applied"""
|
|
custom_bg = (100, 150, 200)
|
|
page = Page(background_color=custom_bg)
|
|
|
|
result = page.render()
|
|
|
|
# Sample multiple points to verify background
|
|
corners = [(0, 0), (799, 0), (0, 599), (799, 599)]
|
|
for corner in corners:
|
|
pixel = result.getpixel(corner)
|
|
self.assertEqual(pixel[:3], custom_bg)
|
|
|
|
def test_page_size_constraints(self):
|
|
"""Test page with various size constraints"""
|
|
small_page = Page(size=(200, 150))
|
|
large_page = Page(size=(1920, 1080))
|
|
|
|
small_result = small_page.render()
|
|
large_result = large_page.render()
|
|
|
|
self.assertEqual(small_result.size, (200, 150))
|
|
self.assertEqual(large_result.size, (1920, 1080))
|
|
|
|
|
|
class TestPageBorderMarginRendering(unittest.TestCase):
|
|
"""Test cases specifically for border/margin consistency in Page rendering"""
|
|
|
|
def setUp(self):
|
|
"""Set up test fixtures for border/margin tests"""
|
|
self.page_size = (400, 300)
|
|
self.padding = (20, 15, 25, 10) # top, right, bottom, left
|
|
self.background_color = (240, 240, 240)
|
|
|
|
def test_border_consistency_with_text_content(self):
|
|
"""Test that borders/margins are consistent when rendering text content"""
|
|
from pyWebLayout.concrete.text import Text
|
|
from pyWebLayout.style.fonts import Font
|
|
|
|
# Create page with specific padding
|
|
page = Page(size=self.page_size, background_color=self.background_color)
|
|
page._padding = self.padding
|
|
|
|
# Add text content - let Text objects calculate their own dimensions
|
|
font = Font(font_size=14)
|
|
text1 = Text("First line of text", font)
|
|
text2 = Text("Second line of text", font)
|
|
|
|
page.add_child(text1)
|
|
page.add_child(text2)
|
|
|
|
# Render the page
|
|
result = page.render()
|
|
|
|
# Extract border areas and verify consistency
|
|
border_measurements = self._extract_border_measurements(result, self.padding)
|
|
self._verify_border_consistency(border_measurements, self.padding)
|
|
|
|
# Verify content area is correctly positioned
|
|
content_area = self._extract_content_area(result, self.padding)
|
|
self.assertIsNotNone(content_area)
|
|
|
|
# Ensure content doesn't bleed into border areas
|
|
self._verify_no_content_in_borders(result, self.padding, self.background_color)
|
|
|
|
def test_border_consistency_with_paragraph_content(self):
|
|
"""Test borders/margins with paragraph content that may wrap"""
|
|
from pyWebLayout.abstract.block import Paragraph
|
|
from pyWebLayout.abstract.inline import Word
|
|
from pyWebLayout.style.fonts import Font
|
|
|
|
# Create a mock paragraph with multiple words
|
|
paragraph = Paragraph()
|
|
font = Font(font_size=12)
|
|
|
|
# Add words to create a longer paragraph
|
|
words_text = ["This", "is", "a", "longer", "paragraph", "that", "should", "wrap", "across", "multiple", "lines", "to", "test", "margin", "consistency"]
|
|
for word_text in words_text:
|
|
word = Word(word_text, font)
|
|
paragraph.add_word(word)
|
|
|
|
# Create page with specific padding
|
|
page = Page(size=self.page_size, background_color=self.background_color)
|
|
page._padding = self.padding
|
|
|
|
# Render paragraph on page
|
|
page.render_blocks([paragraph])
|
|
result = page.render()
|
|
|
|
# Extract and verify border measurements
|
|
border_measurements = self._extract_border_measurements(result, self.padding)
|
|
self._verify_border_consistency(border_measurements, self.padding)
|
|
|
|
# Verify content positioning
|
|
self._verify_content_within_bounds(result, self.padding)
|
|
|
|
def test_border_consistency_with_mixed_content(self):
|
|
"""Test borders/margins with mixed content types"""
|
|
from pyWebLayout.concrete.text import Text
|
|
from pyWebLayout.abstract.block import Paragraph, Heading, HeadingLevel
|
|
from pyWebLayout.abstract.inline import Word
|
|
from pyWebLayout.style.fonts import Font, FontWeight
|
|
|
|
# Create page with asymmetric padding to test edge cases
|
|
asymmetric_padding = (30, 20, 15, 25)
|
|
page = Page(size=(500, 400), background_color=self.background_color)
|
|
page._padding = asymmetric_padding
|
|
|
|
# Create mixed content
|
|
heading = Heading(HeadingLevel.H2)
|
|
heading_font = Font(font_size=18, weight=FontWeight.BOLD)
|
|
heading.add_word(Word("Test Heading", heading_font))
|
|
|
|
paragraph = Paragraph()
|
|
para_font = Font(font_size=12)
|
|
para_words = ["This", "paragraph", "follows", "the", "heading", "and", "tests", "mixed", "content", "rendering"]
|
|
for word_text in para_words:
|
|
paragraph.add_word(Word(word_text, para_font))
|
|
|
|
# Render mixed content
|
|
page.render_blocks([heading, paragraph])
|
|
result = page.render()
|
|
|
|
# Verify border consistency with asymmetric padding
|
|
border_measurements = self._extract_border_measurements(result, asymmetric_padding)
|
|
self._verify_border_consistency(border_measurements, asymmetric_padding)
|
|
|
|
# Verify no content bleeds into margins
|
|
self._verify_no_content_in_borders(result, asymmetric_padding, self.background_color)
|
|
|
|
def test_border_consistency_with_different_padding_values(self):
|
|
"""Test that different padding values maintain consistent borders"""
|
|
from pyWebLayout.concrete.text import Text
|
|
from pyWebLayout.style.fonts import Font
|
|
|
|
padding_configs = [
|
|
(10, 10, 10, 10), # uniform
|
|
(5, 15, 5, 15), # symmetric horizontal/vertical
|
|
(20, 30, 10, 5), # asymmetric
|
|
(0, 5, 0, 5), # minimal top/bottom
|
|
]
|
|
|
|
font = Font(font_size=14)
|
|
|
|
for padding in padding_configs:
|
|
with self.subTest(padding=padding):
|
|
# Create a fresh text object for each test to avoid state issues
|
|
test_text = Text("Border consistency test", font)
|
|
page = Page(size=self.page_size, background_color=self.background_color)
|
|
page._padding = padding
|
|
page.add_child(test_text)
|
|
|
|
result = page.render()
|
|
|
|
# Verify border measurements match expected padding
|
|
border_measurements = self._extract_border_measurements(result, padding)
|
|
self._verify_border_consistency(border_measurements, padding)
|
|
|
|
# Verify content area calculation
|
|
expected_content_width = self.page_size[0] - padding[1] - padding[3] # width - right - left
|
|
expected_content_height = self.page_size[1] - padding[0] - padding[2] # height - top - bottom
|
|
|
|
content_area = self._extract_content_area(result, padding)
|
|
self.assertEqual(content_area['width'], expected_content_width)
|
|
self.assertEqual(content_area['height'], expected_content_height)
|
|
|
|
def test_border_uniformity_across_renders(self):
|
|
"""Test that border areas remain uniform across multiple renders"""
|
|
from pyWebLayout.concrete.text import Text
|
|
from pyWebLayout.style.fonts import Font
|
|
|
|
page = Page(size=self.page_size, background_color=self.background_color)
|
|
page._padding = self.padding
|
|
|
|
font = Font(font_size=12)
|
|
text = Text("Consistency test content", font)
|
|
page.add_child(text)
|
|
|
|
# Render multiple times
|
|
results = []
|
|
for i in range(3):
|
|
result = page.render()
|
|
results.append(result)
|
|
border_measurements = self._extract_border_measurements(result, self.padding)
|
|
|
|
# Store first measurement as baseline
|
|
if i == 0:
|
|
baseline_measurements = border_measurements
|
|
else:
|
|
# Compare with baseline
|
|
self._compare_border_measurements(baseline_measurements, border_measurements)
|
|
|
|
def _extract_border_measurements(self, image, padding):
|
|
"""Extract measurements of border/margin areas from rendered image"""
|
|
width, height = image.size
|
|
top_pad, right_pad, bottom_pad, left_pad = padding
|
|
|
|
measurements = {
|
|
'top_border': {
|
|
'area': (0, 0, width, top_pad),
|
|
'pixels': self._get_area_pixels(image, (0, 0, width, top_pad))
|
|
},
|
|
'right_border': {
|
|
'area': (width - right_pad, 0, width, height),
|
|
'pixels': self._get_area_pixels(image, (width - right_pad, 0, width, height))
|
|
},
|
|
'bottom_border': {
|
|
'area': (0, height - bottom_pad, width, height),
|
|
'pixels': self._get_area_pixels(image, (0, height - bottom_pad, width, height))
|
|
},
|
|
'left_border': {
|
|
'area': (0, 0, left_pad, height),
|
|
'pixels': self._get_area_pixels(image, (0, 0, left_pad, height))
|
|
}
|
|
}
|
|
|
|
return measurements
|
|
|
|
def _get_area_pixels(self, image, area):
|
|
"""Extract pixel data from a specific area of the image"""
|
|
if area[2] <= area[0] or area[3] <= area[1]:
|
|
return [] # Invalid area
|
|
|
|
cropped = image.crop(area)
|
|
return list(cropped.getdata())
|
|
|
|
def _verify_border_consistency(self, measurements, expected_padding):
|
|
"""Verify that border measurements match expected padding values"""
|
|
# Get actual dimensions from the measurements instead of using self.page_size
|
|
# This allows the test to work with different page sizes
|
|
top_area = measurements['top_border']['area']
|
|
width = top_area[2] # right coordinate of top border gives us the width
|
|
height = measurements['left_border']['area'][3] # bottom coordinate of left border gives us the height
|
|
|
|
top_pad, right_pad, bottom_pad, left_pad = expected_padding
|
|
|
|
# Check area dimensions
|
|
self.assertEqual(top_area, (0, 0, width, top_pad))
|
|
|
|
right_area = measurements['right_border']['area']
|
|
self.assertEqual(right_area, (width - right_pad, 0, width, height))
|
|
|
|
bottom_area = measurements['bottom_border']['area']
|
|
self.assertEqual(bottom_area, (0, height - bottom_pad, width, height))
|
|
|
|
left_area = measurements['left_border']['area']
|
|
self.assertEqual(left_area, (0, 0, left_pad, height))
|
|
|
|
def _extract_content_area(self, image, padding):
|
|
"""Extract the content area (area inside borders/margins)"""
|
|
width, height = image.size
|
|
top_pad, right_pad, bottom_pad, left_pad = padding
|
|
|
|
content_area = {
|
|
'left': left_pad,
|
|
'top': top_pad,
|
|
'right': width - right_pad,
|
|
'bottom': height - bottom_pad,
|
|
'width': width - left_pad - right_pad,
|
|
'height': height - top_pad - bottom_pad
|
|
}
|
|
|
|
return content_area
|
|
|
|
def _verify_no_content_in_borders(self, image, padding, background_color):
|
|
"""Verify that no content bleeds into the border/margin areas"""
|
|
measurements = self._extract_border_measurements(image, padding)
|
|
|
|
# Check that border areas contain only background color
|
|
for border_name, border_data in measurements.items():
|
|
pixels = border_data['pixels']
|
|
if pixels: # Only check if area is not empty
|
|
# Most pixels should be background color (allowing for some anti-aliasing)
|
|
bg_count = sum(1 for pixel in pixels if self._is_background_color(pixel, background_color))
|
|
total_pixels = len(pixels)
|
|
|
|
# Allow up to 10% deviation for anti-aliasing effects
|
|
bg_ratio = bg_count / total_pixels if total_pixels > 0 else 1.0
|
|
self.assertGreaterEqual(bg_ratio, 0.9,
|
|
f"Border area '{border_name}' contains too much non-background content. "
|
|
f"Background ratio: {bg_ratio:.2f}")
|
|
|
|
def _is_background_color(self, pixel, background_color, tolerance=10):
|
|
"""Check if a pixel is close to the background color within tolerance"""
|
|
if len(pixel) >= 3:
|
|
r_diff = abs(pixel[0] - background_color[0])
|
|
g_diff = abs(pixel[1] - background_color[1])
|
|
b_diff = abs(pixel[2] - background_color[2])
|
|
return r_diff <= tolerance and g_diff <= tolerance and b_diff <= tolerance
|
|
return False
|
|
|
|
def _verify_content_within_bounds(self, image, padding):
|
|
"""Verify that content is positioned within the expected bounds"""
|
|
content_area = self._extract_content_area(image, padding)
|
|
|
|
# Sample the content area to ensure it's not all background
|
|
if content_area['width'] > 0 and content_area['height'] > 0:
|
|
content_crop = image.crop((
|
|
content_area['left'],
|
|
content_area['top'],
|
|
content_area['right'],
|
|
content_area['bottom']
|
|
))
|
|
|
|
# Content area should have some non-background pixels
|
|
content_pixels = list(content_crop.getdata())
|
|
non_bg_pixels = sum(1 for pixel in content_pixels
|
|
if not self._is_background_color(pixel, self.background_color))
|
|
|
|
# Expect at least some content in the content area
|
|
self.assertGreater(non_bg_pixels, 0, "Content area appears to be empty")
|
|
|
|
def _compare_border_measurements(self, baseline, current):
|
|
"""Compare two sets of border measurements for consistency"""
|
|
for border_name in baseline.keys():
|
|
baseline_area = baseline[border_name]['area']
|
|
current_area = current[border_name]['area']
|
|
|
|
self.assertEqual(baseline_area, current_area,
|
|
f"Border area '{border_name}' is inconsistent between renders")
|
|
|
|
|
|
if __name__ == '__main__':
|
|
unittest.main()
|