422 lines
18 KiB
Python
422 lines
18 KiB
Python
"""
|
|
Unit tests for pyPhotoAlbum alignment system
|
|
"""
|
|
|
|
import pytest
|
|
from pyPhotoAlbum.alignment import AlignmentManager
|
|
from pyPhotoAlbum.models import ImageData, PlaceholderData, TextBoxData
|
|
|
|
|
|
class TestAlignmentManager:
|
|
"""Tests for AlignmentManager class"""
|
|
|
|
def test_get_bounds_empty_list(self):
|
|
"""Test get_bounds with empty list"""
|
|
elements = []
|
|
bounds = AlignmentManager.get_bounds(elements)
|
|
assert bounds == (0, 0, 0, 0)
|
|
|
|
def test_get_bounds_single_element(self):
|
|
"""Test get_bounds with single element"""
|
|
elem = ImageData(x=10, y=20, width=100, height=50)
|
|
bounds = AlignmentManager.get_bounds([elem])
|
|
|
|
# min_x, min_y, max_x, max_y
|
|
assert bounds == (10, 20, 110, 70)
|
|
|
|
def test_get_bounds_multiple_elements(self):
|
|
"""Test get_bounds with multiple elements"""
|
|
elem1 = ImageData(x=10, y=20, width=100, height=50)
|
|
elem2 = ImageData(x=50, y=10, width=80, height=60)
|
|
elem3 = ImageData(x=5, y=30, width=90, height=40)
|
|
|
|
bounds = AlignmentManager.get_bounds([elem1, elem2, elem3])
|
|
|
|
# min_x = 5, min_y = 10, max_x = 130 (50+80), max_y = 70 (10+60 or 20+50)
|
|
assert bounds[0] == 5 # min_x
|
|
assert bounds[1] == 10 # min_y
|
|
assert bounds[2] == 130 # max_x
|
|
assert bounds[3] == 70 # max_y
|
|
|
|
def test_align_left_empty_list(self):
|
|
"""Test align_left with empty list"""
|
|
changes = AlignmentManager.align_left([])
|
|
assert changes == []
|
|
|
|
def test_align_left_single_element(self):
|
|
"""Test align_left with single element"""
|
|
elem = ImageData(x=50, y=50, width=100, height=100)
|
|
changes = AlignmentManager.align_left([elem])
|
|
assert changes == []
|
|
assert elem.position == (50, 50) # Should not change
|
|
|
|
def test_align_left_multiple_elements(self):
|
|
"""Test align_left with multiple elements"""
|
|
elem1 = ImageData(x=50, y=20, width=100, height=50)
|
|
elem2 = ImageData(x=30, y=40, width=80, height=60)
|
|
elem3 = ImageData(x=70, y=60, width=90, height=40)
|
|
|
|
changes = AlignmentManager.align_left([elem1, elem2, elem3])
|
|
|
|
# All should align to x=30 (leftmost)
|
|
assert elem1.position == (30, 20)
|
|
assert elem2.position == (30, 40)
|
|
assert elem3.position == (30, 60)
|
|
|
|
# Check undo information
|
|
assert len(changes) == 3
|
|
assert changes[0] == (elem1, (50, 20))
|
|
assert changes[1] == (elem2, (30, 40))
|
|
assert changes[2] == (elem3, (70, 60))
|
|
|
|
def test_align_right_multiple_elements(self):
|
|
"""Test align_right with multiple elements"""
|
|
elem1 = ImageData(x=50, y=20, width=100, height=50) # right edge at 150
|
|
elem2 = ImageData(x=30, y=40, width=80, height=60) # right edge at 110
|
|
elem3 = ImageData(x=70, y=60, width=90, height=40) # right edge at 160
|
|
|
|
changes = AlignmentManager.align_right([elem1, elem2, elem3])
|
|
|
|
# All right edges should align to x=160 (rightmost)
|
|
assert elem1.position[0] == 60 # 160 - 100
|
|
assert elem2.position[0] == 80 # 160 - 80
|
|
assert elem3.position[0] == 70 # 160 - 90
|
|
|
|
# Y positions should not change
|
|
assert elem1.position[1] == 20
|
|
assert elem2.position[1] == 40
|
|
assert elem3.position[1] == 60
|
|
|
|
def test_align_top_multiple_elements(self):
|
|
"""Test align_top with multiple elements"""
|
|
elem1 = ImageData(x=50, y=30, width=100, height=50)
|
|
elem2 = ImageData(x=30, y=20, width=80, height=60)
|
|
elem3 = ImageData(x=70, y=40, width=90, height=40)
|
|
|
|
changes = AlignmentManager.align_top([elem1, elem2, elem3])
|
|
|
|
# All should align to y=20 (topmost)
|
|
assert elem1.position[1] == 20
|
|
assert elem2.position[1] == 20
|
|
assert elem3.position[1] == 20
|
|
|
|
# X positions should not change
|
|
assert elem1.position[0] == 50
|
|
assert elem2.position[0] == 30
|
|
assert elem3.position[0] == 70
|
|
|
|
def test_align_bottom_multiple_elements(self):
|
|
"""Test align_bottom with multiple elements"""
|
|
elem1 = ImageData(x=50, y=30, width=100, height=50) # bottom at 80
|
|
elem2 = ImageData(x=30, y=20, width=80, height=60) # bottom at 80
|
|
elem3 = ImageData(x=70, y=40, width=90, height=50) # bottom at 90
|
|
|
|
changes = AlignmentManager.align_bottom([elem1, elem2, elem3])
|
|
|
|
# All bottom edges should align to y=90 (bottommost)
|
|
assert elem1.position[1] == 40 # 90 - 50
|
|
assert elem2.position[1] == 30 # 90 - 60
|
|
assert elem3.position[1] == 40 # 90 - 50
|
|
|
|
# X positions should not change
|
|
assert elem1.position[0] == 50
|
|
assert elem2.position[0] == 30
|
|
assert elem3.position[0] == 70
|
|
|
|
def test_align_horizontal_center_multiple_elements(self):
|
|
"""Test align_horizontal_center with multiple elements"""
|
|
elem1 = ImageData(x=50, y=20, width=100, height=50) # center at 100
|
|
elem2 = ImageData(x=30, y=40, width=80, height=60) # center at 70
|
|
elem3 = ImageData(x=70, y=60, width=60, height=40) # center at 100
|
|
|
|
changes = AlignmentManager.align_horizontal_center([elem1, elem2, elem3])
|
|
|
|
# Average center = (100 + 70 + 100) / 3 = 90
|
|
# All elements should center at x=90
|
|
assert abs(elem1.position[0] + elem1.size[0]/2 - 90) < 0.01
|
|
assert abs(elem2.position[0] + elem2.size[0]/2 - 90) < 0.01
|
|
assert abs(elem3.position[0] + elem3.size[0]/2 - 90) < 0.01
|
|
|
|
# Y positions should not change
|
|
assert elem1.position[1] == 20
|
|
assert elem2.position[1] == 40
|
|
assert elem3.position[1] == 60
|
|
|
|
def test_align_vertical_center_multiple_elements(self):
|
|
"""Test align_vertical_center with multiple elements"""
|
|
elem1 = ImageData(x=50, y=20, width=100, height=50) # center at 45
|
|
elem2 = ImageData(x=30, y=40, width=80, height=60) # center at 70
|
|
elem3 = ImageData(x=70, y=30, width=60, height=40) # center at 50
|
|
|
|
changes = AlignmentManager.align_vertical_center([elem1, elem2, elem3])
|
|
|
|
# Average center = (45 + 70 + 50) / 3 = 55
|
|
# All elements should center at y=55
|
|
assert abs(elem1.position[1] + elem1.size[1]/2 - 55) < 0.01
|
|
assert abs(elem2.position[1] + elem2.size[1]/2 - 55) < 0.01
|
|
assert abs(elem3.position[1] + elem3.size[1]/2 - 55) < 0.01
|
|
|
|
# X positions should not change
|
|
assert elem1.position[0] == 50
|
|
assert elem2.position[0] == 30
|
|
assert elem3.position[0] == 70
|
|
|
|
def test_make_same_size_empty_list(self):
|
|
"""Test make_same_size with empty list"""
|
|
changes = AlignmentManager.make_same_size([])
|
|
assert changes == []
|
|
|
|
def test_make_same_size_single_element(self):
|
|
"""Test make_same_size with single element"""
|
|
elem = ImageData(x=50, y=50, width=100, height=100)
|
|
changes = AlignmentManager.make_same_size([elem])
|
|
assert changes == []
|
|
assert elem.size == (100, 100) # Should not change
|
|
|
|
def test_make_same_size_multiple_elements(self):
|
|
"""Test make_same_size with multiple elements"""
|
|
elem1 = ImageData(x=50, y=20, width=100, height=50)
|
|
elem2 = ImageData(x=30, y=40, width=80, height=60)
|
|
elem3 = ImageData(x=70, y=60, width=90, height=40)
|
|
|
|
changes = AlignmentManager.make_same_size([elem1, elem2, elem3])
|
|
|
|
# All should match elem1's size
|
|
assert elem1.size == (100, 50)
|
|
assert elem2.size == (100, 50)
|
|
assert elem3.size == (100, 50)
|
|
|
|
# Check undo information (only elem2 and elem3 change)
|
|
assert len(changes) == 2
|
|
assert changes[0][0] == elem2
|
|
assert changes[0][2] == (80, 60) # old size
|
|
assert changes[1][0] == elem3
|
|
assert changes[1][2] == (90, 40) # old size
|
|
|
|
def test_make_same_width_multiple_elements(self):
|
|
"""Test make_same_width with multiple elements"""
|
|
elem1 = ImageData(x=50, y=20, width=100, height=50)
|
|
elem2 = ImageData(x=30, y=40, width=80, height=60)
|
|
elem3 = ImageData(x=70, y=60, width=90, height=40)
|
|
|
|
changes = AlignmentManager.make_same_width([elem1, elem2, elem3])
|
|
|
|
# All widths should match elem1
|
|
assert elem1.size[0] == 100
|
|
assert elem2.size[0] == 100
|
|
assert elem3.size[0] == 100
|
|
|
|
# Heights should not change
|
|
assert elem1.size[1] == 50
|
|
assert elem2.size[1] == 60
|
|
assert elem3.size[1] == 40
|
|
|
|
def test_make_same_height_multiple_elements(self):
|
|
"""Test make_same_height with multiple elements"""
|
|
elem1 = ImageData(x=50, y=20, width=100, height=50)
|
|
elem2 = ImageData(x=30, y=40, width=80, height=60)
|
|
elem3 = ImageData(x=70, y=60, width=90, height=40)
|
|
|
|
changes = AlignmentManager.make_same_height([elem1, elem2, elem3])
|
|
|
|
# All heights should match elem1
|
|
assert elem1.size[1] == 50
|
|
assert elem2.size[1] == 50
|
|
assert elem3.size[1] == 50
|
|
|
|
# Widths should not change
|
|
assert elem1.size[0] == 100
|
|
assert elem2.size[0] == 80
|
|
assert elem3.size[0] == 90
|
|
|
|
def test_distribute_horizontally_too_few_elements(self):
|
|
"""Test distribute_horizontally with less than 3 elements"""
|
|
elem1 = ImageData(x=50, y=20, width=100, height=50)
|
|
elem2 = ImageData(x=30, y=40, width=80, height=60)
|
|
|
|
changes = AlignmentManager.distribute_horizontally([elem1, elem2])
|
|
assert changes == []
|
|
|
|
def test_distribute_horizontally_multiple_elements(self):
|
|
"""Test distribute_horizontally with multiple elements"""
|
|
elem1 = ImageData(x=0, y=20, width=100, height=50)
|
|
elem2 = ImageData(x=50, y=40, width=80, height=60)
|
|
elem3 = ImageData(x=200, y=60, width=90, height=40)
|
|
|
|
changes = AlignmentManager.distribute_horizontally([elem1, elem2, elem3])
|
|
|
|
# Elements should be distributed evenly by their left edges
|
|
# min_x = 0, max_x = 200, span = 200
|
|
# spacing = 200 / (3-1) = 100
|
|
positions = [elem.position[0] for elem in [elem1, elem2, elem3]]
|
|
sorted_positions = sorted(positions)
|
|
|
|
assert sorted_positions[0] == 0
|
|
assert sorted_positions[1] == 100
|
|
assert sorted_positions[2] == 200
|
|
|
|
def test_distribute_vertically_multiple_elements(self):
|
|
"""Test distribute_vertically with multiple elements"""
|
|
elem1 = ImageData(x=20, y=0, width=100, height=50)
|
|
elem2 = ImageData(x=40, y=50, width=80, height=60)
|
|
elem3 = ImageData(x=60, y=300, width=90, height=40)
|
|
|
|
changes = AlignmentManager.distribute_vertically([elem1, elem2, elem3])
|
|
|
|
# Elements should be distributed evenly by their top edges
|
|
# min_y = 0, max_y = 300, span = 300
|
|
# spacing = 300 / (3-1) = 150
|
|
positions = [elem.position[1] for elem in [elem1, elem2, elem3]]
|
|
sorted_positions = sorted(positions)
|
|
|
|
assert sorted_positions[0] == 0
|
|
assert sorted_positions[1] == 150
|
|
assert sorted_positions[2] == 300
|
|
|
|
def test_space_horizontally_too_few_elements(self):
|
|
"""Test space_horizontally with less than 3 elements"""
|
|
elem1 = ImageData(x=50, y=20, width=100, height=50)
|
|
elem2 = ImageData(x=200, y=40, width=80, height=60)
|
|
|
|
changes = AlignmentManager.space_horizontally([elem1, elem2])
|
|
assert changes == []
|
|
|
|
def test_space_horizontally_multiple_elements(self):
|
|
"""Test space_horizontally with multiple elements"""
|
|
elem1 = ImageData(x=0, y=20, width=100, height=50)
|
|
elem2 = ImageData(x=150, y=40, width=50, height=60)
|
|
elem3 = ImageData(x=250, y=60, width=100, height=40)
|
|
|
|
changes = AlignmentManager.space_horizontally([elem1, elem2, elem3])
|
|
|
|
# Total width = 100 + 50 + 100 = 250
|
|
# Span = 0 to 350 (250 + 100 from elem3)
|
|
# Available space = 350 - 0 - 250 = 100
|
|
# Spacing = 100 / (3-1) = 50
|
|
|
|
# After sorting by x: elem1 at 0, elem2 after 100+50=150, elem3 after 150+50+50=250
|
|
sorted_elements = sorted([elem1, elem2, elem3], key=lambda e: e.position[0])
|
|
|
|
assert sorted_elements[0].position[0] == 0
|
|
assert sorted_elements[1].position[0] == 150 # 0 + 100 + 50
|
|
assert sorted_elements[2].position[0] == 250 # 150 + 50 + 50
|
|
|
|
def test_space_vertically_multiple_elements(self):
|
|
"""Test space_vertically with multiple elements"""
|
|
elem1 = ImageData(x=20, y=0, width=100, height=50)
|
|
elem2 = ImageData(x=40, y=100, width=80, height=30)
|
|
elem3 = ImageData(x=60, y=200, width=90, height=50)
|
|
|
|
changes = AlignmentManager.space_vertically([elem1, elem2, elem3])
|
|
|
|
# Total height = 50 + 30 + 50 = 130
|
|
# Span = 0 to 250 (200 + 50 from elem3)
|
|
# Available space = 250 - 0 - 130 = 120
|
|
# Spacing = 120 / (3-1) = 60
|
|
|
|
# After sorting by y: elem1 at 0, elem2 after 50+60=110, elem3 after 110+30+60=200
|
|
sorted_elements = sorted([elem1, elem2, elem3], key=lambda e: e.position[1])
|
|
|
|
assert sorted_elements[0].position[1] == 0
|
|
assert sorted_elements[1].position[1] == 110 # 0 + 50 + 60
|
|
assert sorted_elements[2].position[1] == 200 # 110 + 30 + 60
|
|
|
|
def test_alignment_with_different_element_types(self):
|
|
"""Test alignment works with different element types"""
|
|
elem1 = ImageData(x=50, y=20, width=100, height=50)
|
|
elem2 = PlaceholderData(placeholder_type="image", x=30, y=40, width=80, height=60)
|
|
elem3 = TextBoxData(text_content="Test", x=70, y=60, width=90, height=40)
|
|
|
|
# Test align_left
|
|
changes = AlignmentManager.align_left([elem1, elem2, elem3])
|
|
|
|
assert elem1.position[0] == 30
|
|
assert elem2.position[0] == 30
|
|
assert elem3.position[0] == 30
|
|
|
|
def test_undo_information_completeness(self):
|
|
"""Test that undo information contains all necessary data"""
|
|
elem1 = ImageData(x=50, y=20, width=100, height=50)
|
|
elem2 = ImageData(x=30, y=40, width=80, height=60)
|
|
elem3 = ImageData(x=70, y=60, width=90, height=40)
|
|
|
|
# Test position changes
|
|
changes = AlignmentManager.align_left([elem1, elem2, elem3])
|
|
|
|
for change in changes:
|
|
assert len(change) == 2 # (element, old_position)
|
|
assert isinstance(change[0], ImageData)
|
|
assert isinstance(change[1], tuple)
|
|
assert len(change[1]) == 2 # (x, y)
|
|
|
|
# Test size changes
|
|
elem1 = ImageData(x=50, y=20, width=100, height=50)
|
|
elem2 = ImageData(x=30, y=40, width=80, height=60)
|
|
elem3 = ImageData(x=70, y=60, width=90, height=40)
|
|
|
|
changes = AlignmentManager.make_same_size([elem1, elem2, elem3])
|
|
|
|
for change in changes:
|
|
assert len(change) == 3 # (element, old_position, old_size)
|
|
assert isinstance(change[0], ImageData)
|
|
assert isinstance(change[1], tuple)
|
|
assert len(change[1]) == 2 # (x, y)
|
|
assert isinstance(change[2], tuple)
|
|
assert len(change[2]) == 2 # (width, height)
|
|
|
|
def test_alignment_preserves_unaffected_properties(self):
|
|
"""Test that alignment operations only change intended properties"""
|
|
elem1 = ImageData(x=50, y=20, width=100, height=50, rotation=45, z_index=5)
|
|
elem2 = ImageData(x=30, y=40, width=80, height=60, rotation=90, z_index=3)
|
|
|
|
AlignmentManager.align_left([elem1, elem2])
|
|
|
|
# Rotation and z_index should not change
|
|
assert elem1.rotation == 45
|
|
assert elem1.z_index == 5
|
|
assert elem2.rotation == 90
|
|
assert elem2.z_index == 3
|
|
|
|
# Heights should not change
|
|
assert elem1.size[1] == 50
|
|
assert elem2.size[1] == 60
|
|
|
|
def test_distribute_with_unsorted_elements(self):
|
|
"""Test distribution works correctly with unsorted input"""
|
|
# Create elements in random order
|
|
elem3 = ImageData(x=200, y=60, width=90, height=40)
|
|
elem1 = ImageData(x=0, y=20, width=100, height=50)
|
|
elem2 = ImageData(x=100, y=40, width=80, height=60)
|
|
|
|
# Pass in random order
|
|
changes = AlignmentManager.distribute_horizontally([elem3, elem1, elem2])
|
|
|
|
# Should still distribute correctly
|
|
positions = sorted([elem1.position[0], elem2.position[0], elem3.position[0]])
|
|
assert positions[0] == 0
|
|
assert positions[1] == 100
|
|
assert positions[2] == 200
|
|
|
|
def test_space_with_varying_sizes(self):
|
|
"""Test spacing works correctly with elements of varying sizes"""
|
|
elem1 = ImageData(x=0, y=0, width=50, height=50)
|
|
elem2 = ImageData(x=100, y=0, width=100, height=50)
|
|
elem3 = ImageData(x=250, y=0, width=75, height=50)
|
|
|
|
changes = AlignmentManager.space_horizontally([elem1, elem2, elem3])
|
|
|
|
# Calculate expected spacing
|
|
# Total width = 50 + 100 + 75 = 225
|
|
# rightmost edge = 250 + 75 = 325
|
|
# Available space = 325 - 0 - 225 = 100
|
|
# Spacing = 100 / 2 = 50
|
|
|
|
sorted_elements = sorted([elem1, elem2, elem3], key=lambda e: e.position[0])
|
|
|
|
# Verify spacing between elements is equal
|
|
gap1 = sorted_elements[1].position[0] - (sorted_elements[0].position[0] + sorted_elements[0].size[0])
|
|
gap2 = sorted_elements[2].position[0] - (sorted_elements[1].position[0] + sorted_elements[1].size[0])
|
|
|
|
assert abs(gap1 - 50) < 0.01
|
|
assert abs(gap2 - 50) < 0.01
|