pyWebLayout/tests/test_concrete_page.py

460 lines
18 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.LEFT)
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))
if __name__ == '__main__':
unittest.main()