474 lines
15 KiB
Python
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
|