All checks were successful
Python CI / test (push) Successful in 1m24s
Lint / lint (push) Successful in 1m13s
Tests / test (3.11) (push) Successful in 1m37s
Tests / test (3.12) (push) Successful in 1m41s
Tests / test (3.13) (push) Successful in 1m34s
Tests / test (3.14) (push) Successful in 1m12s
657 lines
22 KiB
Python
657 lines
22 KiB
Python
"""
|
|
Extended tests for PageLayout to improve coverage
|
|
Tests rendering logic, snapping, and edge cases
|
|
"""
|
|
|
|
import pytest
|
|
from unittest.mock import Mock, MagicMock, patch, call
|
|
from pyPhotoAlbum.page_layout import PageLayout, GridLayout
|
|
from pyPhotoAlbum.models import ImageData, PlaceholderData, TextBoxData
|
|
from pyPhotoAlbum.snapping import SnappingSystem
|
|
|
|
|
|
class TestPageLayoutRendering:
|
|
"""Test PageLayout rendering methods"""
|
|
|
|
@patch("pyPhotoAlbum.page_layout.glDisable")
|
|
@patch("pyPhotoAlbum.page_layout.glEnable")
|
|
@patch("pyPhotoAlbum.page_layout.glColor3f")
|
|
@patch("pyPhotoAlbum.page_layout.glColor4f")
|
|
@patch("pyPhotoAlbum.page_layout.glBegin")
|
|
@patch("pyPhotoAlbum.page_layout.glEnd")
|
|
@patch("pyPhotoAlbum.page_layout.glVertex2f")
|
|
@patch("pyPhotoAlbum.page_layout.glLineWidth")
|
|
@patch("pyPhotoAlbum.page_layout.glBlendFunc")
|
|
def test_render_basic_empty_page(
|
|
self,
|
|
mock_blend,
|
|
mock_linewidth,
|
|
mock_vertex,
|
|
mock_end,
|
|
mock_begin,
|
|
mock_color4f,
|
|
mock_color3f,
|
|
mock_enable,
|
|
mock_disable,
|
|
):
|
|
"""Test rendering empty page calls OpenGL functions correctly"""
|
|
layout = PageLayout(width=210, height=297)
|
|
|
|
# Render with default DPI
|
|
layout.render(dpi=300)
|
|
|
|
# Verify OpenGL depth test was disabled then re-enabled
|
|
assert mock_disable.call_count >= 1
|
|
assert mock_enable.call_count >= 1
|
|
|
|
# Verify colors were set (shadow, background, border)
|
|
assert mock_color3f.call_count >= 3
|
|
|
|
# Verify vertices were drawn (quads for shadow, background, line loop for border)
|
|
assert mock_vertex.call_count >= 8
|
|
|
|
@patch("pyPhotoAlbum.page_layout.glDisable")
|
|
@patch("pyPhotoAlbum.page_layout.glEnable")
|
|
@patch("pyPhotoAlbum.page_layout.glColor3f")
|
|
@patch("pyPhotoAlbum.page_layout.glColor4f")
|
|
@patch("pyPhotoAlbum.page_layout.glBegin")
|
|
@patch("pyPhotoAlbum.page_layout.glEnd")
|
|
@patch("pyPhotoAlbum.page_layout.glVertex2f")
|
|
@patch("pyPhotoAlbum.page_layout.glLineWidth")
|
|
@patch("pyPhotoAlbum.page_layout.glBlendFunc")
|
|
def test_render_facing_page_draws_center_line(
|
|
self,
|
|
mock_blend,
|
|
mock_linewidth,
|
|
mock_vertex,
|
|
mock_end,
|
|
mock_begin,
|
|
mock_color4f,
|
|
mock_color3f,
|
|
mock_enable,
|
|
mock_disable,
|
|
):
|
|
"""Test rendering facing page draws center divider line"""
|
|
layout = PageLayout(width=210, height=297, is_facing_page=True)
|
|
|
|
layout.render(dpi=300)
|
|
|
|
# Verify line width was set for center line
|
|
assert any(call(1.5) in mock_linewidth.call_args_list for call in mock_linewidth.call_args_list)
|
|
|
|
@patch("pyPhotoAlbum.page_layout.glDisable")
|
|
@patch("pyPhotoAlbum.page_layout.glEnable")
|
|
@patch("pyPhotoAlbum.page_layout.glColor3f")
|
|
@patch("pyPhotoAlbum.page_layout.glColor4f")
|
|
@patch("pyPhotoAlbum.page_layout.glBegin")
|
|
@patch("pyPhotoAlbum.page_layout.glEnd")
|
|
@patch("pyPhotoAlbum.page_layout.glVertex2f")
|
|
@patch("pyPhotoAlbum.page_layout.glLineWidth")
|
|
@patch("pyPhotoAlbum.page_layout.glBlendFunc")
|
|
def test_render_with_elements_calls_element_render(
|
|
self,
|
|
mock_blend,
|
|
mock_linewidth,
|
|
mock_vertex,
|
|
mock_end,
|
|
mock_begin,
|
|
mock_color4f,
|
|
mock_color3f,
|
|
mock_enable,
|
|
mock_disable,
|
|
):
|
|
"""Test rendering page with elements calls render on each element"""
|
|
layout = PageLayout()
|
|
|
|
# Add elements with mock render methods
|
|
elem1 = ImageData(image_path="test1.jpg", x=10, y=20)
|
|
elem2 = PlaceholderData(x=30, y=40)
|
|
elem3 = TextBoxData(text_content="Hello", x=50, y=60)
|
|
|
|
elem1.render = Mock()
|
|
elem2.render = Mock()
|
|
elem3.render = Mock()
|
|
|
|
layout.add_element(elem1)
|
|
layout.add_element(elem2)
|
|
layout.add_element(elem3)
|
|
|
|
layout.render(dpi=300)
|
|
|
|
# Verify each element's render was called
|
|
elem1.render.assert_called_once()
|
|
elem2.render.assert_called_once()
|
|
elem3.render.assert_called_once()
|
|
|
|
@patch("pyPhotoAlbum.page_layout.glDisable")
|
|
@patch("pyPhotoAlbum.page_layout.glEnable")
|
|
@patch("pyPhotoAlbum.page_layout.glColor3f")
|
|
@patch("pyPhotoAlbum.page_layout.glColor4f")
|
|
@patch("pyPhotoAlbum.page_layout.glBegin")
|
|
@patch("pyPhotoAlbum.page_layout.glEnd")
|
|
@patch("pyPhotoAlbum.page_layout.glVertex2f")
|
|
@patch("pyPhotoAlbum.page_layout.glLineWidth")
|
|
@patch("pyPhotoAlbum.page_layout.glBlendFunc")
|
|
def test_render_with_image_async_loading_requested(
|
|
self,
|
|
mock_blend,
|
|
mock_linewidth,
|
|
mock_vertex,
|
|
mock_end,
|
|
mock_begin,
|
|
mock_color4f,
|
|
mock_color3f,
|
|
mock_enable,
|
|
mock_disable,
|
|
):
|
|
"""Test rendering image without texture triggers async load request"""
|
|
layout = PageLayout()
|
|
|
|
# Create mock parent widget with async loader
|
|
mock_parent = Mock()
|
|
mock_parent.async_image_loader = Mock()
|
|
mock_parent.request_image_load = Mock()
|
|
layout._parent_widget = mock_parent
|
|
|
|
# Add image element without texture
|
|
elem = ImageData(image_path="test.jpg", x=10, y=20)
|
|
elem.render = Mock()
|
|
elem._async_load_requested = False
|
|
layout.add_element(elem)
|
|
|
|
layout.render(dpi=300)
|
|
|
|
# Verify async load was requested
|
|
assert elem._async_load_requested is True
|
|
assert elem._async_loading is True
|
|
mock_parent.request_image_load.assert_called_once()
|
|
|
|
@patch("pyPhotoAlbum.page_layout.glDisable")
|
|
@patch("pyPhotoAlbum.page_layout.glEnable")
|
|
@patch("pyPhotoAlbum.page_layout.glColor3f")
|
|
@patch("pyPhotoAlbum.page_layout.glColor4f")
|
|
@patch("pyPhotoAlbum.page_layout.glBegin")
|
|
@patch("pyPhotoAlbum.page_layout.glEnd")
|
|
@patch("pyPhotoAlbum.page_layout.glVertex2f")
|
|
@patch("pyPhotoAlbum.page_layout.glLineWidth")
|
|
@patch("pyPhotoAlbum.page_layout.glBlendFunc")
|
|
def test_render_with_different_dpi_scales_correctly(
|
|
self,
|
|
mock_blend,
|
|
mock_linewidth,
|
|
mock_vertex,
|
|
mock_end,
|
|
mock_begin,
|
|
mock_color4f,
|
|
mock_color3f,
|
|
mock_enable,
|
|
mock_disable,
|
|
):
|
|
"""Test rendering with different DPI values"""
|
|
layout = PageLayout(width=210, height=297)
|
|
|
|
# Render with different DPIs
|
|
layout.render(dpi=72)
|
|
layout.render(dpi=150)
|
|
layout.render(dpi=600)
|
|
|
|
# Should not raise any errors
|
|
assert mock_vertex.call_count > 0
|
|
|
|
|
|
class TestPageLayoutSnapLines:
|
|
"""Test snap line rendering"""
|
|
|
|
@patch("pyPhotoAlbum.page_layout.glDisable")
|
|
@patch("pyPhotoAlbum.page_layout.glEnable")
|
|
@patch("pyPhotoAlbum.page_layout.glColor3f")
|
|
@patch("pyPhotoAlbum.page_layout.glColor4f")
|
|
@patch("pyPhotoAlbum.page_layout.glBegin")
|
|
@patch("pyPhotoAlbum.page_layout.glEnd")
|
|
@patch("pyPhotoAlbum.page_layout.glVertex2f")
|
|
@patch("pyPhotoAlbum.page_layout.glLineWidth")
|
|
@patch("pyPhotoAlbum.page_layout.glBlendFunc")
|
|
def test_render_snap_lines_with_project_settings(
|
|
self,
|
|
mock_blend,
|
|
mock_linewidth,
|
|
mock_vertex,
|
|
mock_end,
|
|
mock_begin,
|
|
mock_color4f,
|
|
mock_color3f,
|
|
mock_enable,
|
|
mock_disable,
|
|
):
|
|
"""Test snap lines use project settings when available"""
|
|
layout = PageLayout()
|
|
|
|
# Create mock project with global snapping settings
|
|
mock_project = Mock()
|
|
mock_project.snap_to_grid = True
|
|
mock_project.snap_to_edges = True
|
|
mock_project.snap_to_guides = True
|
|
mock_project.grid_size_mm = 10.0
|
|
mock_project.snap_threshold_mm = 5.0
|
|
mock_project.show_grid = True
|
|
mock_project.show_snap_lines = True
|
|
|
|
layout.render(dpi=300, project=mock_project)
|
|
|
|
# Verify blending was enabled for transparent lines
|
|
# Check that glEnable was called (for GL_BLEND)
|
|
assert mock_enable.call_count > 0
|
|
|
|
@patch("pyPhotoAlbum.page_layout.glDisable")
|
|
@patch("pyPhotoAlbum.page_layout.glEnable")
|
|
@patch("pyPhotoAlbum.page_layout.glColor3f")
|
|
@patch("pyPhotoAlbum.page_layout.glColor4f")
|
|
@patch("pyPhotoAlbum.page_layout.glBegin")
|
|
@patch("pyPhotoAlbum.page_layout.glEnd")
|
|
@patch("pyPhotoAlbum.page_layout.glVertex2f")
|
|
@patch("pyPhotoAlbum.page_layout.glLineWidth")
|
|
@patch("pyPhotoAlbum.page_layout.glBlendFunc")
|
|
def test_render_snap_lines_fallback_to_local_settings(
|
|
self,
|
|
mock_blend,
|
|
mock_linewidth,
|
|
mock_vertex,
|
|
mock_end,
|
|
mock_begin,
|
|
mock_color4f,
|
|
mock_color3f,
|
|
mock_enable,
|
|
mock_disable,
|
|
):
|
|
"""Test snap lines fall back to local settings when no project"""
|
|
layout = PageLayout()
|
|
layout.snapping_system.snap_to_grid = True
|
|
layout.snapping_system.grid_size_mm = 15.0
|
|
|
|
# Render without project
|
|
layout.render(dpi=300, project=None)
|
|
|
|
# Should complete without error
|
|
assert mock_vertex.call_count > 0
|
|
|
|
@patch("pyPhotoAlbum.page_layout.glDisable")
|
|
@patch("pyPhotoAlbum.page_layout.glEnable")
|
|
@patch("pyPhotoAlbum.page_layout.glColor3f")
|
|
@patch("pyPhotoAlbum.page_layout.glColor4f")
|
|
@patch("pyPhotoAlbum.page_layout.glBegin")
|
|
@patch("pyPhotoAlbum.page_layout.glEnd")
|
|
@patch("pyPhotoAlbum.page_layout.glVertex2f")
|
|
@patch("pyPhotoAlbum.page_layout.glLineWidth")
|
|
@patch("pyPhotoAlbum.page_layout.glBlendFunc")
|
|
def test_render_snap_lines_with_guides(
|
|
self,
|
|
mock_blend,
|
|
mock_linewidth,
|
|
mock_vertex,
|
|
mock_end,
|
|
mock_begin,
|
|
mock_color4f,
|
|
mock_color3f,
|
|
mock_enable,
|
|
mock_disable,
|
|
):
|
|
"""Test rendering snap lines with custom guides"""
|
|
layout = PageLayout()
|
|
|
|
# Add custom guides using the proper method (position, orientation)
|
|
layout.snapping_system.add_guide(100, "vertical")
|
|
layout.snapping_system.add_guide(150, "horizontal")
|
|
|
|
mock_project = Mock()
|
|
mock_project.snap_to_grid = False
|
|
mock_project.snap_to_edges = False
|
|
mock_project.snap_to_guides = True
|
|
mock_project.grid_size_mm = 10.0
|
|
mock_project.snap_threshold_mm = 5.0
|
|
mock_project.show_grid = False
|
|
mock_project.show_snap_lines = True
|
|
|
|
layout.render(dpi=300, project=mock_project)
|
|
|
|
# Cyan color should be used for guides (0.0, 0.7, 0.9)
|
|
# Check if cyan color was set (at least once)
|
|
cyan_calls = [call for call in mock_color3f.call_args_list if call[0] == (0.0, 0.7, 0.9)]
|
|
assert len(cyan_calls) > 0, "Cyan color for guides should be set"
|
|
|
|
|
|
class TestPageLayoutEdgeCases:
|
|
"""Test edge cases and error conditions"""
|
|
|
|
def test_page_layout_with_zero_dimensions(self):
|
|
"""Test page layout with very small dimensions"""
|
|
layout = PageLayout(width=0.1, height=0.1)
|
|
assert layout.size == (0.1, 0.1)
|
|
|
|
def test_page_layout_with_large_dimensions(self):
|
|
"""Test page layout with very large dimensions"""
|
|
layout = PageLayout(width=10000, height=10000)
|
|
assert layout.size == (10000, 10000)
|
|
|
|
def test_multiple_grid_layout_changes(self):
|
|
"""Test changing grid layout multiple times"""
|
|
layout = PageLayout()
|
|
|
|
grid1 = GridLayout(rows=2, columns=2)
|
|
grid2 = GridLayout(rows=3, columns=3)
|
|
grid3 = GridLayout(rows=4, columns=4)
|
|
|
|
layout.set_grid_layout(grid1)
|
|
assert layout.grid_layout == grid1
|
|
|
|
layout.set_grid_layout(grid2)
|
|
assert layout.grid_layout == grid2
|
|
|
|
layout.set_grid_layout(grid3)
|
|
assert layout.grid_layout == grid3
|
|
|
|
def test_deserialize_with_missing_snapping_system(self):
|
|
"""Test deserialization handles missing snapping_system gracefully"""
|
|
layout = PageLayout()
|
|
data = {
|
|
"size": (210, 297),
|
|
"elements": [],
|
|
# No snapping_system key
|
|
}
|
|
|
|
layout.deserialize(data)
|
|
|
|
# Should use existing snapping system
|
|
assert layout.snapping_system is not None
|
|
|
|
def test_deserialize_element_with_invalid_crop_info(self):
|
|
"""Test deserialization handles elements with missing optional fields"""
|
|
layout = PageLayout()
|
|
data = {
|
|
"size": (210, 297),
|
|
"elements": [
|
|
{
|
|
"type": "image",
|
|
"image_path": "test.jpg",
|
|
"position": (10, 20),
|
|
"size": (100, 100),
|
|
"rotation": 0,
|
|
"z_index": 0,
|
|
# Missing crop_info - should use default
|
|
}
|
|
],
|
|
}
|
|
|
|
layout.deserialize(data)
|
|
|
|
assert len(layout.elements) == 1
|
|
assert isinstance(layout.elements[0], ImageData)
|
|
|
|
def test_element_z_index_handling(self):
|
|
"""Test that elements maintain correct z-order"""
|
|
layout = PageLayout()
|
|
|
|
elem1 = ImageData(image_path="test1.jpg", x=10, y=20)
|
|
elem2 = ImageData(image_path="test2.jpg", x=30, y=40)
|
|
elem3 = ImageData(image_path="test3.jpg", x=50, y=60)
|
|
|
|
elem1.z_index = 10
|
|
elem2.z_index = 5
|
|
elem3.z_index = 15
|
|
|
|
layout.add_element(elem1)
|
|
layout.add_element(elem2)
|
|
layout.add_element(elem3)
|
|
|
|
# Elements added in order, z_index is just metadata
|
|
assert layout.elements[0] == elem1
|
|
assert layout.elements[1] == elem2
|
|
assert layout.elements[2] == elem3
|
|
|
|
|
|
class TestGridLayoutEdgeCases:
|
|
"""Test GridLayout edge cases"""
|
|
|
|
def test_grid_with_single_cell(self):
|
|
"""Test grid layout with single cell (1x1)"""
|
|
grid = GridLayout(rows=1, columns=1, spacing=5.0)
|
|
|
|
pos = grid.get_cell_position(0, 0, page_width=100, page_height=100)
|
|
size = grid.get_cell_size(page_width=100, page_height=100)
|
|
|
|
assert pos == (5.0, 5.0)
|
|
# (100 - 10) / 1 = 90
|
|
assert size == (90.0, 90.0)
|
|
|
|
def test_grid_with_no_spacing(self):
|
|
"""Test grid layout with zero spacing"""
|
|
grid = GridLayout(rows=2, columns=2, spacing=0.0)
|
|
|
|
pos = grid.get_cell_position(0, 0, page_width=100, page_height=100)
|
|
size = grid.get_cell_size(page_width=100, page_height=100)
|
|
|
|
assert pos == (0.0, 0.0)
|
|
assert size == (50.0, 50.0)
|
|
|
|
def test_grid_with_large_spacing(self):
|
|
"""Test grid layout with large spacing"""
|
|
grid = GridLayout(rows=2, columns=2, spacing=20.0)
|
|
|
|
size = grid.get_cell_size(page_width=200, page_height=200)
|
|
|
|
# (200 - 60) / 2 = 70
|
|
assert size == (70.0, 70.0)
|
|
|
|
def test_grid_merge_multiple_cells(self):
|
|
"""Test merging many cells"""
|
|
grid = GridLayout(rows=5, columns=5)
|
|
|
|
for row in range(3):
|
|
for col in range(3):
|
|
grid.merge_cells(row, col)
|
|
|
|
assert len(grid.merged_cells) == 9
|
|
|
|
def test_grid_cell_position_at_boundaries(self):
|
|
"""Test cell positions at grid boundaries"""
|
|
grid = GridLayout(rows=3, columns=3, spacing=10.0)
|
|
|
|
# Top-left corner
|
|
pos_tl = grid.get_cell_position(0, 0, page_width=300, page_height=300)
|
|
assert pos_tl[0] == 10.0
|
|
assert pos_tl[1] == 10.0
|
|
|
|
# Bottom-right corner (2, 2)
|
|
pos_br = grid.get_cell_position(2, 2, page_width=300, page_height=300)
|
|
# (300 - 40) / 3 = 86.67, x = 10 + 2 * (86.67 + 10) = 203.33
|
|
assert pos_br[0] == pytest.approx(203.333, rel=0.01)
|
|
assert pos_br[1] == pytest.approx(203.333, rel=0.01)
|
|
|
|
|
|
class TestPageLayoutSnappingIntegration:
|
|
"""Test integration with snapping system"""
|
|
|
|
def test_snapping_system_initialized(self):
|
|
"""Test that PageLayout initializes with snapping system"""
|
|
layout = PageLayout()
|
|
assert isinstance(layout.snapping_system, SnappingSystem)
|
|
|
|
def test_snapping_system_serialization(self):
|
|
"""Test that snapping system is included in serialization"""
|
|
layout = PageLayout()
|
|
layout.snapping_system.snap_to_grid = True
|
|
layout.snapping_system.grid_size_mm = 20.0
|
|
|
|
data = layout.serialize()
|
|
|
|
assert "snapping_system" in data
|
|
assert data["snapping_system"]["snap_to_grid"] is True
|
|
assert data["snapping_system"]["grid_size_mm"] == 20.0
|
|
|
|
def test_snapping_system_deserialization(self):
|
|
"""Test that snapping system is restored from serialization"""
|
|
layout = PageLayout()
|
|
|
|
# First create a layout with snapping settings
|
|
layout.snapping_system.snap_to_grid = True
|
|
layout.snapping_system.snap_to_edges = False
|
|
layout.snapping_system.snap_to_guides = True
|
|
layout.snapping_system.grid_size_mm = 25.0
|
|
layout.snapping_system.snap_threshold_mm = 3.0
|
|
layout.snapping_system.add_guide(100, "vertical")
|
|
layout.snapping_system.add_guide(150, "horizontal")
|
|
|
|
# Serialize and deserialize
|
|
data = layout.serialize()
|
|
restored = PageLayout()
|
|
restored.deserialize(data)
|
|
|
|
assert restored.snapping_system.snap_to_grid is True
|
|
assert restored.snapping_system.snap_to_edges is False
|
|
assert restored.snapping_system.snap_to_guides is True
|
|
assert restored.snapping_system.grid_size_mm == 25.0
|
|
assert restored.snapping_system.snap_threshold_mm == 3.0
|
|
assert len(restored.snapping_system.guides) == 2
|
|
|
|
|
|
class TestPageLayoutBackwardCompatibility:
|
|
"""Test backward compatibility with older data formats"""
|
|
|
|
def test_deserialize_without_base_width(self):
|
|
"""Test deserializing data without base_width field"""
|
|
layout = PageLayout()
|
|
data = {
|
|
"size": (200, 280),
|
|
# No base_width field
|
|
"elements": [],
|
|
}
|
|
|
|
layout.deserialize(data)
|
|
|
|
# Should default to width from size
|
|
assert layout.base_width == 200
|
|
|
|
def test_deserialize_without_is_facing_page(self):
|
|
"""Test deserializing data without is_facing_page field"""
|
|
layout = PageLayout()
|
|
data = {
|
|
"size": (210, 297),
|
|
# No is_facing_page field
|
|
"elements": [],
|
|
}
|
|
|
|
layout.deserialize(data)
|
|
|
|
# Should default to False
|
|
assert layout.is_facing_page is False
|
|
|
|
def test_deserialize_without_show_snap_lines(self):
|
|
"""Test deserializing data without show_snap_lines field"""
|
|
layout = PageLayout()
|
|
data = {
|
|
"size": (210, 297),
|
|
"elements": [],
|
|
# No show_snap_lines field
|
|
}
|
|
|
|
layout.deserialize(data)
|
|
|
|
# Should default to True
|
|
assert layout.show_snap_lines is True
|
|
|
|
|
|
class TestPageLayoutComplexScenarios:
|
|
"""Test complex real-world scenarios"""
|
|
|
|
def test_layout_with_many_elements(self):
|
|
"""Test layout with large number of elements"""
|
|
layout = PageLayout()
|
|
|
|
# Add 100 elements
|
|
for i in range(100):
|
|
elem = ImageData(image_path=f"test{i}.jpg", x=i * 10, y=i * 10)
|
|
elem.z_index = i
|
|
layout.add_element(elem)
|
|
|
|
assert len(layout.elements) == 100
|
|
|
|
# Serialize and deserialize
|
|
data = layout.serialize()
|
|
restored = PageLayout()
|
|
restored.deserialize(data)
|
|
|
|
assert len(restored.elements) == 100
|
|
# Elements should be sorted by z_index
|
|
for i in range(100):
|
|
assert restored.elements[i].z_index == i
|
|
|
|
def test_layout_with_mixed_element_types(self):
|
|
"""Test layout with all element types mixed"""
|
|
layout = PageLayout()
|
|
|
|
for i in range(10):
|
|
if i % 3 == 0:
|
|
elem = ImageData(image_path=f"img{i}.jpg", x=i * 20, y=i * 20)
|
|
elif i % 3 == 1:
|
|
elem = PlaceholderData(x=i * 20, y=i * 20)
|
|
else:
|
|
elem = TextBoxData(text_content=f"Text {i}", x=i * 20, y=i * 20)
|
|
|
|
elem.z_index = i
|
|
layout.add_element(elem)
|
|
|
|
data = layout.serialize()
|
|
restored = PageLayout()
|
|
restored.deserialize(data)
|
|
|
|
# Count element types
|
|
images = sum(1 for e in restored.elements if isinstance(e, ImageData))
|
|
placeholders = sum(1 for e in restored.elements if isinstance(e, PlaceholderData))
|
|
textboxes = sum(1 for e in restored.elements if isinstance(e, TextBoxData))
|
|
|
|
assert images == 4 # 0, 3, 6, 9
|
|
assert placeholders == 3 # 1, 4, 7
|
|
assert textboxes == 3 # 2, 5, 8
|
|
|
|
@patch("pyPhotoAlbum.page_layout.glDisable")
|
|
@patch("pyPhotoAlbum.page_layout.glEnable")
|
|
@patch("pyPhotoAlbum.page_layout.glColor3f")
|
|
@patch("pyPhotoAlbum.page_layout.glColor4f")
|
|
@patch("pyPhotoAlbum.page_layout.glBegin")
|
|
@patch("pyPhotoAlbum.page_layout.glEnd")
|
|
@patch("pyPhotoAlbum.page_layout.glVertex2f")
|
|
@patch("pyPhotoAlbum.page_layout.glLineWidth")
|
|
@patch("pyPhotoAlbum.page_layout.glBlendFunc")
|
|
def test_render_facing_page_with_elements(
|
|
self,
|
|
mock_blend,
|
|
mock_linewidth,
|
|
mock_vertex,
|
|
mock_end,
|
|
mock_begin,
|
|
mock_color4f,
|
|
mock_color3f,
|
|
mock_enable,
|
|
mock_disable,
|
|
):
|
|
"""Test rendering facing page with elements"""
|
|
layout = PageLayout(width=210, height=297, is_facing_page=True)
|
|
|
|
# Add elements on both sides
|
|
left_elem = ImageData(image_path="left.jpg", x=50, y=100)
|
|
right_elem = ImageData(image_path="right.jpg", x=250, y=100)
|
|
|
|
left_elem.render = Mock()
|
|
right_elem.render = Mock()
|
|
|
|
layout.add_element(left_elem)
|
|
layout.add_element(right_elem)
|
|
|
|
layout.render(dpi=300)
|
|
|
|
# Both elements should be rendered
|
|
left_elem.render.assert_called_once()
|
|
right_elem.render.assert_called_once()
|
|
|
|
# Center line should be drawn
|
|
assert any(call(1.5) in mock_linewidth.call_args_list for call in mock_linewidth.call_args_list)
|