pyPhotoAlbum/tests/test_page_layout.py
2025-11-11 16:02:02 +00:00

474 lines
15 KiB
Python

"""
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