""" 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)) if __name__ == '__main__': unittest.main()