460 lines
18 KiB
Python
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()
|