""" Tests for PageLayout and GridLayout classes """ import pytest from pyPhotoAlbum.page_layout import PageLayout, GridLayout from pyPhotoAlbum.models import ImageData, PlaceholderData, TextBoxData from pyPhotoAlbum.snapping import SnappingSystem class TestPageLayoutInitialization: """Test PageLayout initialization""" def test_initialization_default(self): """Test PageLayout with default values""" layout = PageLayout() assert layout.size == (210, 297) assert layout.base_width == 210 assert layout.is_facing_page is False assert layout.elements == [] assert layout.grid_layout is None assert layout.background_color == (1.0, 1.0, 1.0) assert isinstance(layout.snapping_system, SnappingSystem) assert layout.show_snap_lines is True def test_initialization_custom_size(self): """Test PageLayout with custom dimensions""" layout = PageLayout(width=150, height=200) assert layout.size == (150, 200) assert layout.base_width == 150 assert layout.is_facing_page is False def test_initialization_facing_page(self): """Test PageLayout as facing page (double width)""" layout = PageLayout(width=210, height=297, is_facing_page=True) assert layout.size == (420, 297) # Width doubled assert layout.base_width == 210 assert layout.is_facing_page is True class TestElementManagement: """Test element add/remove operations""" def test_add_element(self): """Test adding element to layout""" layout = PageLayout() elem = ImageData(image_path="test.jpg") layout.add_element(elem) assert len(layout.elements) == 1 assert layout.elements[0] == elem def test_add_multiple_elements(self): """Test adding multiple elements""" layout = PageLayout() elem1 = ImageData(image_path="test1.jpg") elem2 = PlaceholderData() elem3 = TextBoxData(text_content="Hello") layout.add_element(elem1) layout.add_element(elem2) layout.add_element(elem3) assert len(layout.elements) == 3 assert layout.elements[0] == elem1 assert layout.elements[1] == elem2 assert layout.elements[2] == elem3 def test_remove_element(self): """Test removing element from layout""" layout = PageLayout() elem1 = ImageData(image_path="test1.jpg") elem2 = PlaceholderData() layout.add_element(elem1) layout.add_element(elem2) layout.remove_element(elem1) assert len(layout.elements) == 1 assert layout.elements[0] == elem2 def test_remove_element_not_in_list_raises_error(self): """Test removing non-existent element raises error""" layout = PageLayout() elem = ImageData(image_path="test.jpg") with pytest.raises(ValueError): layout.remove_element(elem) class TestGridLayout: """Test setting grid layout""" def test_set_grid_layout(self): """Test setting a grid layout""" layout = PageLayout() grid = GridLayout(rows=2, columns=3) layout.set_grid_layout(grid) assert layout.grid_layout == grid assert layout.grid_layout.rows == 2 assert layout.grid_layout.columns == 3 class TestPageLayoutSerialization: """Test PageLayout serialization""" def test_serialize_empty_layout(self): """Test serializing empty layout""" layout = PageLayout(width=210, height=297) data = layout.serialize() assert data["size"] == (210, 297) assert data["base_width"] == 210 assert data["is_facing_page"] is False assert data["background_color"] == (1.0, 1.0, 1.0) assert data["elements"] == [] assert data["grid_layout"] is None assert "snapping_system" in data assert data["show_snap_lines"] is True def test_serialize_with_elements(self): """Test serializing layout with elements""" layout = PageLayout() elem1 = ImageData(image_path="test1.jpg", x=10, y=20) elem2 = PlaceholderData(x=30, y=40) layout.add_element(elem1) layout.add_element(elem2) data = layout.serialize() assert len(data["elements"]) == 2 assert data["elements"][0]["type"] == "image" assert data["elements"][1]["type"] == "placeholder" def test_serialize_with_grid_layout(self): """Test serializing layout with grid""" layout = PageLayout() grid = GridLayout(rows=3, columns=2, spacing=15.0) layout.set_grid_layout(grid) data = layout.serialize() assert data["grid_layout"] is not None assert data["grid_layout"]["rows"] == 3 assert data["grid_layout"]["columns"] == 2 assert data["grid_layout"]["spacing"] == 15.0 def test_serialize_facing_page(self): """Test serializing facing page layout""" layout = PageLayout(width=210, height=297, is_facing_page=True) data = layout.serialize() assert data["size"] == (420, 297) assert data["base_width"] == 210 assert data["is_facing_page"] is True class TestPageLayoutDeserialization: """Test PageLayout deserialization""" def test_deserialize_basic_layout(self): """Test deserializing basic layout""" layout = PageLayout() data = { "size": (150, 200), "base_width": 150, "is_facing_page": False, "background_color": (0.9, 0.9, 0.9), "elements": [], "grid_layout": None, "snapping_system": {}, "show_snap_lines": False } layout.deserialize(data) assert layout.size == (150, 200) assert layout.base_width == 150 assert layout.is_facing_page is False assert layout.background_color == (0.9, 0.9, 0.9) assert layout.show_snap_lines is False def test_deserialize_with_elements(self): """Test deserializing layout with elements""" layout = PageLayout() data = { "size": (210, 297), "elements": [ { "type": "image", "image_path": "test1.jpg", "position": (10, 20), "size": (100, 100), "rotation": 0, "z_index": 0, "crop_info": (0, 0, 1, 1) }, { "type": "placeholder", "placeholder_type": "image", "default_content": "", "position": (30, 40), "size": (80, 80), "rotation": 0, "z_index": 1 }, { "type": "textbox", "text_content": "Hello", "font_settings": {"family": "Arial", "size": 12, "color": (0, 0, 0)}, "alignment": "left", "position": (50, 60), "size": (120, 40), "rotation": 0, "z_index": 2 } ] } layout.deserialize(data) assert len(layout.elements) == 3 assert isinstance(layout.elements[0], ImageData) assert isinstance(layout.elements[1], PlaceholderData) assert isinstance(layout.elements[2], TextBoxData) def test_deserialize_elements_sorted_by_z_index(self): """Test elements are sorted by z_index during deserialization""" layout = PageLayout() data = { "size": (210, 297), "elements": [ { "type": "image", "image_path": "test1.jpg", "position": (10, 20), "size": (100, 100), "rotation": 0, "z_index": 5, # Higher z_index "crop_info": (0, 0, 1, 1) }, { "type": "placeholder", "placeholder_type": "image", "default_content": "", "position": (30, 40), "size": (80, 80), "rotation": 0, "z_index": 1 # Lower z_index - should be first } ] } layout.deserialize(data) assert len(layout.elements) == 2 # Lower z_index should come first in list assert layout.elements[0].z_index == 1 assert layout.elements[1].z_index == 5 def test_deserialize_with_grid_layout(self): """Test deserializing layout with grid""" layout = PageLayout() data = { "size": (210, 297), "elements": [], "grid_layout": { "rows": 2, "columns": 3, "spacing": 12.5, "merged_cells": [(0, 0), (1, 1)] } } layout.deserialize(data) assert layout.grid_layout is not None assert layout.grid_layout.rows == 2 assert layout.grid_layout.columns == 3 assert layout.grid_layout.spacing == 12.5 assert layout.grid_layout.merged_cells == [(0, 0), (1, 1)] def test_deserialize_unknown_element_type_skipped(self): """Test that unknown element types are skipped""" layout = PageLayout() data = { "size": (210, 297), "elements": [ { "type": "unknown_type", "position": (10, 20) }, { "type": "image", "image_path": "test.jpg", "position": (30, 40), "size": (100, 100), "rotation": 0, "z_index": 0, "crop_info": (0, 0, 1, 1) } ] } layout.deserialize(data) # Only valid element should be deserialized assert len(layout.elements) == 1 assert isinstance(layout.elements[0], ImageData) def test_deserialize_with_defaults(self): """Test deserialization with missing fields uses defaults""" layout = PageLayout() data = {} layout.deserialize(data) assert layout.size == (210, 297) assert layout.background_color == (1.0, 1.0, 1.0) assert layout.show_snap_lines is True class TestSerializationRoundtrip: """Test serialization/deserialization roundtrips""" def test_serialize_deserialize_roundtrip(self): """Test that serialize/deserialize are inverse operations""" original = PageLayout(width=200, height=280, is_facing_page=False) original.background_color = (0.95, 0.95, 0.95) original.show_snap_lines = False elem1 = ImageData(image_path="test.jpg", x=10, y=20, width=100, height=80) elem2 = PlaceholderData(x=30, y=40, width=50, height=50) original.add_element(elem1) original.add_element(elem2) grid = GridLayout(rows=2, columns=3, spacing=10.0) original.set_grid_layout(grid) # Serialize and deserialize data = original.serialize() restored = PageLayout() restored.deserialize(data) # Verify restoration assert restored.size == original.size assert restored.base_width == original.base_width assert restored.is_facing_page == original.is_facing_page assert restored.background_color == original.background_color assert restored.show_snap_lines == original.show_snap_lines assert len(restored.elements) == len(original.elements) assert restored.grid_layout is not None assert restored.grid_layout.rows == original.grid_layout.rows class TestGridLayoutClass: """Test GridLayout class""" def test_initialization_default(self): """Test GridLayout initialization with defaults""" grid = GridLayout() assert grid.rows == 1 assert grid.columns == 1 assert grid.spacing == 10.0 assert grid.merged_cells == [] def test_initialization_custom(self): """Test GridLayout initialization with custom values""" grid = GridLayout(rows=3, columns=4, spacing=15.5) assert grid.rows == 3 assert grid.columns == 4 assert grid.spacing == 15.5 def test_merge_cells(self): """Test merging cells in grid""" grid = GridLayout(rows=3, columns=3) grid.merge_cells(0, 0) grid.merge_cells(1, 1) assert len(grid.merged_cells) == 2 assert (0, 0) in grid.merged_cells assert (1, 1) in grid.merged_cells def test_get_cell_position(self): """Test calculating cell position""" grid = GridLayout(rows=2, columns=2, spacing=10.0) # Cell (0, 0) - top left x, y = grid.get_cell_position(0, 0, page_width=800, page_height=600) assert x == 10.0 # spacing assert y == 10.0 # spacing # Cell (0, 1) - top right x, y = grid.get_cell_position(0, 1, page_width=800, page_height=600) # spacing + col * (cell_width + spacing) # cell_width = (800 - 30) / 2 = 385 # x = 10 + 1 * (385 + 10) = 405 assert x == 405.0 def test_get_cell_size(self): """Test calculating cell size""" grid = GridLayout(rows=2, columns=3, spacing=10.0) width, height = grid.get_cell_size(page_width=900, page_height=600) # width = (900 - 10 * 4) / 3 = 860 / 3 ≈ 286.67 # height = (600 - 10 * 3) / 2 = 570 / 2 = 285 assert width == pytest.approx(286.666, rel=0.01) assert height == 285.0 def test_grid_serialization(self): """Test GridLayout serialization""" grid = GridLayout(rows=3, columns=2, spacing=12.0) grid.merge_cells(0, 1) grid.merge_cells(2, 0) data = grid.serialize() assert data["rows"] == 3 assert data["columns"] == 2 assert data["spacing"] == 12.0 assert data["merged_cells"] == [(0, 1), (2, 0)] def test_grid_deserialization(self): """Test GridLayout deserialization""" grid = GridLayout() data = { "rows": 4, "columns": 5, "spacing": 8.5, "merged_cells": [(1, 2), (3, 3)] } grid.deserialize(data) assert grid.rows == 4 assert grid.columns == 5 assert grid.spacing == 8.5 assert grid.merged_cells == [(1, 2), (3, 3)] def test_grid_deserialization_with_defaults(self): """Test GridLayout deserialization with missing fields""" grid = GridLayout() data = {} grid.deserialize(data) assert grid.rows == 1 assert grid.columns == 1 assert grid.spacing == 10.0 assert grid.merged_cells == [] def test_grid_serialize_deserialize_roundtrip(self): """Test GridLayout serialize/deserialize roundtrip""" original = GridLayout(rows=3, columns=4, spacing=11.5) original.merge_cells(0, 0) original.merge_cells(1, 2) original.merge_cells(2, 3) data = original.serialize() restored = GridLayout() restored.deserialize(data) assert restored.rows == original.rows assert restored.columns == original.columns assert restored.spacing == original.spacing assert restored.merged_cells == original.merged_cells