533 lines
19 KiB
Python
533 lines
19 KiB
Python
"""
|
|
Unit tests for pyPhotoAlbum template management system
|
|
"""
|
|
|
|
import pytest
|
|
import tempfile
|
|
import json
|
|
from pathlib import Path
|
|
from pyPhotoAlbum.template_manager import Template, TemplateManager
|
|
from pyPhotoAlbum.models import ImageData, PlaceholderData, TextBoxData
|
|
from pyPhotoAlbum.page_layout import PageLayout
|
|
from pyPhotoAlbum.project import Page
|
|
|
|
|
|
class TestTemplate:
|
|
"""Tests for Template class"""
|
|
|
|
def test_initialization_default(self):
|
|
"""Test Template initialization with default values"""
|
|
template = Template()
|
|
assert template.name == "Untitled Template"
|
|
assert template.description == ""
|
|
assert template.page_size_mm == (210, 297)
|
|
assert len(template.elements) == 0
|
|
|
|
def test_initialization_with_parameters(self):
|
|
"""Test Template initialization with custom parameters"""
|
|
template = Template(
|
|
name="My Template",
|
|
description="Test template",
|
|
page_size_mm=(200, 280)
|
|
)
|
|
assert template.name == "My Template"
|
|
assert template.description == "Test template"
|
|
assert template.page_size_mm == (200, 280)
|
|
|
|
def test_add_element(self):
|
|
"""Test adding elements to template"""
|
|
template = Template()
|
|
placeholder = PlaceholderData(x=10, y=20, width=100, height=50)
|
|
|
|
template.add_element(placeholder)
|
|
assert len(template.elements) == 1
|
|
assert template.elements[0] == placeholder
|
|
|
|
def test_add_multiple_elements(self):
|
|
"""Test adding multiple elements"""
|
|
template = Template()
|
|
elem1 = PlaceholderData(x=10, y=20, width=100, height=50)
|
|
elem2 = TextBoxData(text_content="Test", x=30, y=40, width=150, height=60)
|
|
|
|
template.add_element(elem1)
|
|
template.add_element(elem2)
|
|
|
|
assert len(template.elements) == 2
|
|
assert elem1 in template.elements
|
|
assert elem2 in template.elements
|
|
|
|
def test_to_dict(self):
|
|
"""Test serialization to dictionary"""
|
|
template = Template(name="Test", description="Desc", page_size_mm=(200, 280))
|
|
placeholder = PlaceholderData(x=10, y=20, width=100, height=50)
|
|
template.add_element(placeholder)
|
|
|
|
data = template.to_dict()
|
|
|
|
assert data["name"] == "Test"
|
|
assert data["description"] == "Desc"
|
|
assert data["page_size_mm"] == (200, 280)
|
|
assert len(data["elements"]) == 1
|
|
assert data["elements"][0]["type"] == "placeholder"
|
|
|
|
def test_from_dict(self):
|
|
"""Test deserialization from dictionary"""
|
|
data = {
|
|
"name": "Loaded Template",
|
|
"description": "Test description",
|
|
"page_size_mm": [220, 300],
|
|
"elements": [
|
|
{
|
|
"type": "placeholder",
|
|
"position": (50, 60),
|
|
"size": (120, 80),
|
|
"placeholder_type": "image"
|
|
},
|
|
{
|
|
"type": "textbox",
|
|
"position": (70, 90),
|
|
"size": (140, 100),
|
|
"text_content": "Test text"
|
|
}
|
|
]
|
|
}
|
|
|
|
template = Template.from_dict(data)
|
|
|
|
assert template.name == "Loaded Template"
|
|
assert template.description == "Test description"
|
|
assert template.page_size_mm == (220, 300)
|
|
assert len(template.elements) == 2
|
|
assert isinstance(template.elements[0], PlaceholderData)
|
|
assert isinstance(template.elements[1], TextBoxData)
|
|
|
|
def test_from_dict_skips_image_elements(self):
|
|
"""Test that from_dict skips image elements"""
|
|
data = {
|
|
"name": "Test",
|
|
"elements": [
|
|
{"type": "image", "position": (10, 20), "size": (100, 50)},
|
|
{"type": "placeholder", "position": (30, 40), "size": (120, 60)}
|
|
]
|
|
}
|
|
|
|
template = Template.from_dict(data)
|
|
|
|
# Should only have the placeholder, not the image
|
|
assert len(template.elements) == 1
|
|
assert isinstance(template.elements[0], PlaceholderData)
|
|
|
|
def test_save_to_file(self, temp_dir):
|
|
"""Test saving template to file"""
|
|
template = Template(name="Save Test", description="Test save")
|
|
placeholder = PlaceholderData(x=10, y=20, width=100, height=50)
|
|
template.add_element(placeholder)
|
|
|
|
file_path = Path(temp_dir) / "test_template.json"
|
|
template.save_to_file(str(file_path))
|
|
|
|
# Verify file was created
|
|
assert file_path.exists()
|
|
|
|
# Verify content
|
|
with open(file_path, 'r') as f:
|
|
data = json.load(f)
|
|
assert data["name"] == "Save Test"
|
|
assert data["description"] == "Test save"
|
|
|
|
def test_load_from_file(self, temp_dir):
|
|
"""Test loading template from file"""
|
|
# Create a test file
|
|
data = {
|
|
"name": "Load Test",
|
|
"description": "Test load",
|
|
"page_size_mm": [210, 297],
|
|
"elements": [
|
|
{
|
|
"type": "placeholder",
|
|
"position": (10, 20),
|
|
"size": (100, 50),
|
|
"placeholder_type": "image"
|
|
}
|
|
]
|
|
}
|
|
|
|
file_path = Path(temp_dir) / "load_test.json"
|
|
with open(file_path, 'w') as f:
|
|
json.dump(data, f)
|
|
|
|
# Load template
|
|
template = Template.load_from_file(str(file_path))
|
|
|
|
assert template.name == "Load Test"
|
|
assert template.description == "Test load"
|
|
assert len(template.elements) == 1
|
|
|
|
|
|
class TestTemplateManager:
|
|
"""Tests for TemplateManager class"""
|
|
|
|
def test_initialization(self):
|
|
"""Test TemplateManager initialization"""
|
|
manager = TemplateManager()
|
|
assert manager.templates_dir is not None
|
|
assert isinstance(manager.templates_dir, Path)
|
|
|
|
def test_get_templates_directory(self):
|
|
"""Test getting templates directory"""
|
|
manager = TemplateManager()
|
|
templates_dir = manager._get_templates_directory()
|
|
|
|
assert templates_dir.name == "templates"
|
|
assert ".pyphotoalbum" in str(templates_dir)
|
|
|
|
def test_get_builtin_templates_directory(self):
|
|
"""Test getting built-in templates directory"""
|
|
manager = TemplateManager()
|
|
builtin_dir = manager._get_builtin_templates_directory()
|
|
|
|
assert builtin_dir.name == "templates"
|
|
assert "pyPhotoAlbum" in str(builtin_dir)
|
|
|
|
def test_list_templates_empty(self, tmp_path, monkeypatch):
|
|
"""Test listing templates when directory is empty"""
|
|
# Create temporary directories
|
|
user_dir = tmp_path / "user_templates"
|
|
builtin_dir = tmp_path / "builtin_templates"
|
|
user_dir.mkdir()
|
|
builtin_dir.mkdir()
|
|
|
|
manager = TemplateManager()
|
|
monkeypatch.setattr(manager, 'templates_dir', user_dir)
|
|
monkeypatch.setattr(manager, '_get_builtin_templates_directory', lambda: builtin_dir)
|
|
|
|
templates = manager.list_templates()
|
|
assert templates == []
|
|
|
|
def test_list_templates_with_files(self, tmp_path, monkeypatch):
|
|
"""Test listing templates with template files"""
|
|
user_dir = tmp_path / "user_templates"
|
|
builtin_dir = tmp_path / "builtin_templates"
|
|
user_dir.mkdir()
|
|
builtin_dir.mkdir()
|
|
|
|
# Create user template
|
|
user_template = user_dir / "My_Template.json"
|
|
user_template.write_text('{"name": "My Template"}')
|
|
|
|
# Create built-in template
|
|
builtin_template = builtin_dir / "Grid_2x2.json"
|
|
builtin_template.write_text('{"name": "Grid 2x2"}')
|
|
|
|
manager = TemplateManager()
|
|
monkeypatch.setattr(manager, 'templates_dir', user_dir)
|
|
monkeypatch.setattr(manager, '_get_builtin_templates_directory', lambda: builtin_dir)
|
|
|
|
templates = manager.list_templates()
|
|
|
|
assert "[Built-in] Grid_2x2" in templates
|
|
assert "My_Template" in templates
|
|
assert len(templates) == 2
|
|
|
|
def test_save_template(self, tmp_path, monkeypatch):
|
|
"""Test saving a template"""
|
|
user_dir = tmp_path / "user_templates"
|
|
user_dir.mkdir()
|
|
|
|
manager = TemplateManager()
|
|
monkeypatch.setattr(manager, 'templates_dir', user_dir)
|
|
|
|
template = Template(name="Test Template")
|
|
manager.save_template(template)
|
|
|
|
# Verify file was created
|
|
template_file = user_dir / "Test Template.json"
|
|
assert template_file.exists()
|
|
|
|
def test_load_template_user(self, tmp_path, monkeypatch):
|
|
"""Test loading a user template"""
|
|
user_dir = tmp_path / "user_templates"
|
|
user_dir.mkdir()
|
|
|
|
# Create template file
|
|
data = {"name": "User Template", "description": "Test", "page_size_mm": [210, 297], "elements": []}
|
|
template_file = user_dir / "User Template.json"
|
|
with open(template_file, 'w') as f:
|
|
json.dump(data, f)
|
|
|
|
manager = TemplateManager()
|
|
monkeypatch.setattr(manager, 'templates_dir', user_dir)
|
|
|
|
template = manager.load_template("User Template")
|
|
assert template.name == "User Template"
|
|
|
|
def test_load_template_builtin(self, tmp_path, monkeypatch):
|
|
"""Test loading a built-in template"""
|
|
builtin_dir = tmp_path / "builtin_templates"
|
|
builtin_dir.mkdir()
|
|
|
|
# Create built-in template file
|
|
data = {"name": "Grid 2x2", "description": "Built-in grid", "page_size_mm": [210, 297], "elements": []}
|
|
template_file = builtin_dir / "Grid 2x2.json"
|
|
with open(template_file, 'w') as f:
|
|
json.dump(data, f)
|
|
|
|
manager = TemplateManager()
|
|
monkeypatch.setattr(manager, '_get_builtin_templates_directory', lambda: builtin_dir)
|
|
|
|
template = manager.load_template("[Built-in] Grid 2x2")
|
|
assert template.name == "Grid 2x2"
|
|
|
|
def test_load_template_not_found(self, tmp_path, monkeypatch):
|
|
"""Test loading non-existent template raises error"""
|
|
user_dir = tmp_path / "user_templates"
|
|
user_dir.mkdir()
|
|
|
|
manager = TemplateManager()
|
|
monkeypatch.setattr(manager, 'templates_dir', user_dir)
|
|
|
|
with pytest.raises(FileNotFoundError):
|
|
manager.load_template("NonExistent")
|
|
|
|
def test_delete_template(self, tmp_path, monkeypatch):
|
|
"""Test deleting a user template"""
|
|
user_dir = tmp_path / "user_templates"
|
|
user_dir.mkdir()
|
|
|
|
# Create template file
|
|
template_file = user_dir / "DeleteMe.json"
|
|
template_file.write_text('{"name": "DeleteMe"}')
|
|
|
|
manager = TemplateManager()
|
|
monkeypatch.setattr(manager, 'templates_dir', user_dir)
|
|
|
|
manager.delete_template("DeleteMe")
|
|
assert not template_file.exists()
|
|
|
|
def test_delete_builtin_template_raises_error(self):
|
|
"""Test deleting built-in template raises error"""
|
|
manager = TemplateManager()
|
|
|
|
with pytest.raises(PermissionError):
|
|
manager.delete_template("[Built-in] Grid_2x2")
|
|
|
|
def test_create_template_from_page(self):
|
|
"""Test creating template from a page"""
|
|
# Create a page with various elements
|
|
layout = PageLayout(width=210, height=297)
|
|
img = ImageData(image_path="test.jpg", x=10, y=20, width=100, height=50)
|
|
text = TextBoxData(text_content="Test", x=30, y=40, width=150, height=60)
|
|
placeholder = PlaceholderData(x=50, y=60, width=120, height=70)
|
|
|
|
layout.add_element(img)
|
|
layout.add_element(text)
|
|
layout.add_element(placeholder)
|
|
|
|
page = Page(layout=layout, page_number=1)
|
|
|
|
# Create template
|
|
manager = TemplateManager()
|
|
template = manager.create_template_from_page(
|
|
page,
|
|
name="Test Template",
|
|
description="Created from page"
|
|
)
|
|
|
|
assert template.name == "Test Template"
|
|
assert template.description == "Created from page"
|
|
assert len(template.elements) == 3
|
|
|
|
# Image should be converted to placeholder
|
|
assert isinstance(template.elements[0], PlaceholderData)
|
|
assert isinstance(template.elements[1], TextBoxData)
|
|
assert isinstance(template.elements[2], PlaceholderData)
|
|
|
|
def test_scale_template_elements_proportional(self):
|
|
"""Test scaling template elements proportionally"""
|
|
manager = TemplateManager()
|
|
|
|
# Create elements at 200x200 size
|
|
elem = PlaceholderData(x=50, y=50, width=100, height=100)
|
|
elements = [elem]
|
|
|
|
# Scale to 400x400 (2x scale)
|
|
scaled = manager.scale_template_elements(
|
|
elements,
|
|
from_size=(200, 200),
|
|
to_size=(400, 400),
|
|
scale_mode="proportional"
|
|
)
|
|
|
|
assert len(scaled) == 1
|
|
# With proportional scaling and centering
|
|
# scale = min(400/200, 400/200) = 2.0
|
|
# offset = (400 - 200*2) / 2 = 0
|
|
assert scaled[0].position == (100, 100) # 50 * 2 + 0
|
|
assert scaled[0].size == (200, 200) # 100 * 2
|
|
|
|
def test_scale_template_elements_stretch(self):
|
|
"""Test scaling template elements with stretch mode"""
|
|
manager = TemplateManager()
|
|
|
|
elem = PlaceholderData(x=50, y=50, width=100, height=100)
|
|
elements = [elem]
|
|
|
|
# Scale to 400x200 (2x width, 1x height)
|
|
scaled = manager.scale_template_elements(
|
|
elements,
|
|
from_size=(200, 200),
|
|
to_size=(400, 200),
|
|
scale_mode="stretch"
|
|
)
|
|
|
|
assert len(scaled) == 1
|
|
assert scaled[0].position == (100, 50) # 50 * 2, 50 * 1
|
|
assert scaled[0].size == (200, 100) # 100 * 2, 100 * 1
|
|
|
|
def test_scale_template_elements_center(self):
|
|
"""Test scaling template elements with center mode"""
|
|
manager = TemplateManager()
|
|
|
|
elem = PlaceholderData(x=50, y=50, width=100, height=100)
|
|
elements = [elem]
|
|
|
|
# Center in larger space without scaling
|
|
scaled = manager.scale_template_elements(
|
|
elements,
|
|
from_size=(200, 200),
|
|
to_size=(400, 400),
|
|
scale_mode="center"
|
|
)
|
|
|
|
assert len(scaled) == 1
|
|
# offset = (400 - 200) / 2 = 100
|
|
assert scaled[0].position == (150, 150) # 50 + 100
|
|
assert scaled[0].size == (100, 100) # No scaling
|
|
|
|
def test_scale_template_preserves_properties(self):
|
|
"""Test that scaling preserves element properties"""
|
|
manager = TemplateManager()
|
|
|
|
elem = PlaceholderData(x=50, y=50, width=100, height=100)
|
|
elem.rotation = 45
|
|
elem.z_index = 5
|
|
elem.placeholder_type = "image"
|
|
|
|
scaled = manager.scale_template_elements(
|
|
[elem],
|
|
from_size=(200, 200),
|
|
to_size=(400, 400),
|
|
scale_mode="proportional"
|
|
)
|
|
|
|
assert scaled[0].rotation == 45
|
|
assert scaled[0].z_index == 5
|
|
assert scaled[0].placeholder_type == "image"
|
|
|
|
def test_apply_template_to_page_replace(self):
|
|
"""Test applying template with replace mode"""
|
|
manager = TemplateManager()
|
|
|
|
# Create template
|
|
template = Template(page_size_mm=(200, 200))
|
|
template.add_element(PlaceholderData(x=10, y=20, width=80, height=60))
|
|
|
|
# Create page with existing content
|
|
layout = PageLayout(width=200, height=200)
|
|
layout.add_element(ImageData(x=100, y=100, width=50, height=50))
|
|
page = Page(layout=layout, page_number=1)
|
|
|
|
# Apply template
|
|
manager.apply_template_to_page(template, page, mode="replace")
|
|
|
|
# Page should have only template elements
|
|
assert len(page.layout.elements) == 1
|
|
assert isinstance(page.layout.elements[0], PlaceholderData)
|
|
|
|
def test_apply_template_to_page_reflow(self):
|
|
"""Test applying template with reflow mode"""
|
|
manager = TemplateManager()
|
|
|
|
# Create template with 2 placeholders
|
|
template = Template(page_size_mm=(200, 200))
|
|
template.add_element(PlaceholderData(x=10, y=20, width=80, height=60))
|
|
template.add_element(PlaceholderData(x=100, y=100, width=80, height=60))
|
|
|
|
# Create page with 1 image
|
|
layout = PageLayout(width=200, height=200)
|
|
img = ImageData(image_path="test.jpg", x=50, y=50, width=50, height=50)
|
|
layout.add_element(img)
|
|
page = Page(layout=layout, page_number=1)
|
|
|
|
# Apply template with reflow
|
|
manager.apply_template_to_page(template, page, mode="reflow")
|
|
|
|
# Should have 1 image (reflowed) + 1 placeholder
|
|
assert len(page.layout.elements) == 2
|
|
# First should be the reflowed image
|
|
assert isinstance(page.layout.elements[0], ImageData)
|
|
# Second should be placeholder (no image to fill it)
|
|
assert isinstance(page.layout.elements[1], PlaceholderData)
|
|
|
|
def test_create_page_from_template_default_size(self):
|
|
"""Test creating page from template with default size"""
|
|
manager = TemplateManager()
|
|
|
|
# Create template
|
|
template = Template(page_size_mm=(210, 297))
|
|
template.add_element(PlaceholderData(x=10, y=20, width=100, height=50))
|
|
|
|
# Create page
|
|
page = manager.create_page_from_template(template, page_number=5)
|
|
|
|
assert page.page_number == 5
|
|
assert page.layout.size == (210, 297)
|
|
assert len(page.layout.elements) == 1
|
|
assert isinstance(page.layout.elements[0], PlaceholderData)
|
|
|
|
def test_create_page_from_template_custom_size(self):
|
|
"""Test creating page from template with custom size"""
|
|
manager = TemplateManager()
|
|
|
|
# Create template at 200x200
|
|
template = Template(page_size_mm=(200, 200))
|
|
template.add_element(PlaceholderData(x=50, y=50, width=100, height=100))
|
|
|
|
# Create page at 400x400
|
|
page = manager.create_page_from_template(
|
|
template,
|
|
page_number=1,
|
|
target_size_mm=(400, 400),
|
|
scale_mode="proportional"
|
|
)
|
|
|
|
assert page.layout.size == (400, 400)
|
|
assert len(page.layout.elements) == 1
|
|
# Element should be scaled
|
|
assert page.layout.elements[0].size == (200, 200) # 100 * 2
|
|
|
|
def test_scale_with_textbox_preserves_font_settings(self):
|
|
"""Test that scaling preserves text box font settings"""
|
|
manager = TemplateManager()
|
|
|
|
font_settings = {"family": "Arial", "size": 12, "color": (0, 0, 0)}
|
|
text = TextBoxData(
|
|
text_content="Test",
|
|
font_settings=font_settings,
|
|
x=50,
|
|
y=50,
|
|
width=100,
|
|
height=50
|
|
)
|
|
|
|
scaled = manager.scale_template_elements(
|
|
[text],
|
|
from_size=(200, 200),
|
|
to_size=(400, 400),
|
|
scale_mode="proportional"
|
|
)
|
|
|
|
assert scaled[0].text_content == "Test"
|
|
assert scaled[0].font_settings == font_settings
|
|
assert scaled[0].alignment == text.alignment
|