382 lines
13 KiB
Python
382 lines
13 KiB
Python
"""
|
|
Unit tests for z-order operations in pyPhotoAlbum
|
|
"""
|
|
|
|
import pytest
|
|
from pyPhotoAlbum.models import ImageData, TextBoxData, PlaceholderData
|
|
from pyPhotoAlbum.page_layout import PageLayout
|
|
from pyPhotoAlbum.commands import ChangeZOrderCommand, CommandHistory
|
|
|
|
|
|
class TestZOrderBasics:
|
|
"""Tests for basic z-order functionality"""
|
|
|
|
def test_list_order_is_render_order(self):
|
|
"""Test that list order determines render order"""
|
|
layout = PageLayout(width=210, height=297)
|
|
|
|
# Add elements in order
|
|
elem1 = ImageData(x=10, y=10, width=50, height=50)
|
|
elem2 = TextBoxData(x=20, y=20, width=50, height=50)
|
|
elem3 = PlaceholderData(x=30, y=30, width=50, height=50)
|
|
|
|
layout.add_element(elem1)
|
|
layout.add_element(elem2)
|
|
layout.add_element(elem3)
|
|
|
|
# Verify order
|
|
assert layout.elements[0] is elem1
|
|
assert layout.elements[1] is elem2
|
|
assert layout.elements[2] is elem3
|
|
|
|
def test_element_at_end_renders_on_top(self):
|
|
"""Test that element at end of list renders on top"""
|
|
layout = PageLayout(width=210, height=297)
|
|
|
|
elem1 = ImageData(x=10, y=10)
|
|
elem2 = ImageData(x=20, y=20)
|
|
|
|
layout.add_element(elem1)
|
|
layout.add_element(elem2)
|
|
|
|
# elem2 should be last (on top)
|
|
assert layout.elements[-1] is elem2
|
|
assert layout.elements.index(elem2) > layout.elements.index(elem1)
|
|
|
|
|
|
class TestChangeZOrderCommand:
|
|
"""Tests for ChangeZOrderCommand"""
|
|
|
|
def test_move_element_forward(self):
|
|
"""Test moving an element forward one position"""
|
|
layout = PageLayout()
|
|
elem1 = ImageData(x=10, y=10)
|
|
elem2 = TextBoxData(x=20, y=20)
|
|
elem3 = PlaceholderData(x=30, y=30)
|
|
|
|
layout.add_element(elem1)
|
|
layout.add_element(elem2)
|
|
layout.add_element(elem3)
|
|
|
|
# Move elem1 forward (swap with elem2)
|
|
cmd = ChangeZOrderCommand(layout, elem1, old_index=0, new_index=1)
|
|
cmd.execute()
|
|
|
|
assert layout.elements.index(elem1) == 1
|
|
assert layout.elements.index(elem2) == 0
|
|
assert layout.elements.index(elem3) == 2
|
|
|
|
def test_move_element_backward(self):
|
|
"""Test moving an element backward one position"""
|
|
layout = PageLayout()
|
|
elem1 = ImageData(x=10, y=10)
|
|
elem2 = TextBoxData(x=20, y=20)
|
|
elem3 = PlaceholderData(x=30, y=30)
|
|
|
|
layout.add_element(elem1)
|
|
layout.add_element(elem2)
|
|
layout.add_element(elem3)
|
|
|
|
# Move elem2 backward (swap with elem1)
|
|
cmd = ChangeZOrderCommand(layout, elem2, old_index=1, new_index=0)
|
|
cmd.execute()
|
|
|
|
assert layout.elements.index(elem2) == 0
|
|
assert layout.elements.index(elem1) == 1
|
|
assert layout.elements.index(elem3) == 2
|
|
|
|
def test_move_to_front(self):
|
|
"""Test moving an element to the front (end of list)"""
|
|
layout = PageLayout()
|
|
elem1 = ImageData(x=10, y=10)
|
|
elem2 = TextBoxData(x=20, y=20)
|
|
elem3 = PlaceholderData(x=30, y=30)
|
|
|
|
layout.add_element(elem1)
|
|
layout.add_element(elem2)
|
|
layout.add_element(elem3)
|
|
|
|
# Move elem1 to front
|
|
cmd = ChangeZOrderCommand(layout, elem1, old_index=0, new_index=2)
|
|
cmd.execute()
|
|
|
|
assert layout.elements[-1] is elem1
|
|
assert layout.elements.index(elem1) == 2
|
|
|
|
def test_move_to_back(self):
|
|
"""Test moving an element to the back (start of list)"""
|
|
layout = PageLayout()
|
|
elem1 = ImageData(x=10, y=10)
|
|
elem2 = TextBoxData(x=20, y=20)
|
|
elem3 = PlaceholderData(x=30, y=30)
|
|
|
|
layout.add_element(elem1)
|
|
layout.add_element(elem2)
|
|
layout.add_element(elem3)
|
|
|
|
# Move elem3 to back
|
|
cmd = ChangeZOrderCommand(layout, elem3, old_index=2, new_index=0)
|
|
cmd.execute()
|
|
|
|
assert layout.elements[0] is elem3
|
|
assert layout.elements.index(elem3) == 0
|
|
|
|
def test_undo_redo(self):
|
|
"""Test undo/redo functionality"""
|
|
layout = PageLayout()
|
|
elem1 = ImageData(x=10, y=10)
|
|
elem2 = TextBoxData(x=20, y=20)
|
|
elem3 = PlaceholderData(x=30, y=30)
|
|
|
|
layout.add_element(elem1)
|
|
layout.add_element(elem2)
|
|
layout.add_element(elem3)
|
|
|
|
original_order = list(layout.elements)
|
|
|
|
# Move elem1 forward
|
|
cmd = ChangeZOrderCommand(layout, elem1, old_index=0, new_index=1)
|
|
cmd.execute()
|
|
|
|
assert layout.elements.index(elem1) == 1
|
|
|
|
# Undo
|
|
cmd.undo()
|
|
assert layout.elements == original_order
|
|
|
|
# Redo
|
|
cmd.redo()
|
|
assert layout.elements.index(elem1) == 1
|
|
|
|
def test_command_with_history(self):
|
|
"""Test ChangeZOrderCommand with CommandHistory"""
|
|
layout = PageLayout()
|
|
history = CommandHistory()
|
|
|
|
elem1 = ImageData(x=10, y=10)
|
|
elem2 = TextBoxData(x=20, y=20)
|
|
elem3 = PlaceholderData(x=30, y=30)
|
|
|
|
layout.add_element(elem1)
|
|
layout.add_element(elem2)
|
|
layout.add_element(elem3)
|
|
|
|
# Execute command through history
|
|
cmd = ChangeZOrderCommand(layout, elem1, old_index=0, new_index=2)
|
|
history.execute(cmd)
|
|
|
|
assert layout.elements.index(elem1) == 2
|
|
assert history.can_undo()
|
|
|
|
# Undo through history
|
|
history.undo()
|
|
assert layout.elements.index(elem1) == 0
|
|
assert history.can_redo()
|
|
|
|
# Redo through history
|
|
history.redo()
|
|
assert layout.elements.index(elem1) == 2
|
|
|
|
|
|
class TestZOrderSerialization:
|
|
"""Tests for z-order serialization and deserialization"""
|
|
|
|
def test_serialize_preserves_order(self):
|
|
"""Test that serialization preserves element order"""
|
|
layout = PageLayout()
|
|
elem1 = ImageData(x=10, y=10, z_index=0)
|
|
elem2 = TextBoxData(x=20, y=20, z_index=1)
|
|
elem3 = PlaceholderData(x=30, y=30, z_index=2)
|
|
|
|
layout.add_element(elem1)
|
|
layout.add_element(elem2)
|
|
layout.add_element(elem3)
|
|
|
|
# Serialize
|
|
data = layout.serialize()
|
|
|
|
# Elements should be in order
|
|
assert len(data['elements']) == 3
|
|
assert data['elements'][0]['type'] == 'image'
|
|
assert data['elements'][1]['type'] == 'textbox'
|
|
assert data['elements'][2]['type'] == 'placeholder'
|
|
|
|
def test_deserialize_sorts_by_zindex(self):
|
|
"""Test that deserialization sorts by z_index for backward compatibility"""
|
|
layout = PageLayout()
|
|
|
|
# Create data with z_index values out of order
|
|
data = {
|
|
'size': (210, 297),
|
|
'base_width': 210,
|
|
'is_facing_page': False,
|
|
'background_color': (1.0, 1.0, 1.0),
|
|
'elements': [
|
|
{'type': 'image', 'position': (10, 10), 'size': (50, 50),
|
|
'rotation': 0, 'z_index': 2, 'image_path': '', 'crop_info': (0, 0, 1, 1)},
|
|
{'type': 'textbox', 'position': (20, 20), 'size': (50, 50),
|
|
'rotation': 0, 'z_index': 0, 'text_content': '',
|
|
'font_settings': {}, 'alignment': 'left'},
|
|
{'type': 'placeholder', 'position': (30, 30), 'size': (50, 50),
|
|
'rotation': 0, 'z_index': 1, 'placeholder_type': 'image', 'default_content': ''},
|
|
]
|
|
}
|
|
|
|
layout.deserialize(data)
|
|
|
|
# Elements should be sorted by z_index
|
|
assert len(layout.elements) == 3
|
|
assert isinstance(layout.elements[0], TextBoxData) # z_index=0
|
|
assert isinstance(layout.elements[1], PlaceholderData) # z_index=1
|
|
assert isinstance(layout.elements[2], ImageData) # z_index=2
|
|
|
|
def test_roundtrip_maintains_order(self):
|
|
"""Test that serialize/deserialize maintains element order"""
|
|
layout1 = PageLayout()
|
|
elem1 = ImageData(x=10, y=10, z_index=0)
|
|
elem2 = TextBoxData(x=20, y=20, z_index=1)
|
|
elem3 = PlaceholderData(x=30, y=30, z_index=2)
|
|
|
|
layout1.add_element(elem1)
|
|
layout1.add_element(elem2)
|
|
layout1.add_element(elem3)
|
|
|
|
# Serialize and deserialize
|
|
data = layout1.serialize()
|
|
layout2 = PageLayout()
|
|
layout2.deserialize(data)
|
|
|
|
# Order should be maintained
|
|
assert len(layout2.elements) == 3
|
|
assert isinstance(layout2.elements[0], ImageData)
|
|
assert isinstance(layout2.elements[1], TextBoxData)
|
|
assert isinstance(layout2.elements[2], PlaceholderData)
|
|
|
|
|
|
class TestZOrderEdgeCases:
|
|
"""Tests for z-order edge cases"""
|
|
|
|
def test_single_element(self):
|
|
"""Test operations with single element"""
|
|
layout = PageLayout()
|
|
elem = ImageData(x=10, y=10)
|
|
layout.add_element(elem)
|
|
|
|
# Try to move forward (should stay at index 0)
|
|
cmd = ChangeZOrderCommand(layout, elem, old_index=0, new_index=0)
|
|
cmd.execute()
|
|
|
|
assert layout.elements.index(elem) == 0
|
|
|
|
def test_empty_list(self):
|
|
"""Test operations with empty list"""
|
|
layout = PageLayout()
|
|
assert len(layout.elements) == 0
|
|
|
|
def test_move_to_same_position(self):
|
|
"""Test moving element to its current position"""
|
|
layout = PageLayout()
|
|
elem1 = ImageData(x=10, y=10)
|
|
elem2 = TextBoxData(x=20, y=20)
|
|
|
|
layout.add_element(elem1)
|
|
layout.add_element(elem2)
|
|
|
|
# Move to same position
|
|
cmd = ChangeZOrderCommand(layout, elem1, old_index=0, new_index=0)
|
|
cmd.execute()
|
|
|
|
assert layout.elements.index(elem1) == 0
|
|
assert layout.elements.index(elem2) == 1
|
|
|
|
def test_swap_adjacent_elements(self):
|
|
"""Test swapping two adjacent elements"""
|
|
layout = PageLayout()
|
|
elem1 = ImageData(x=10, y=10)
|
|
elem2 = TextBoxData(x=20, y=20)
|
|
|
|
layout.add_element(elem1)
|
|
layout.add_element(elem2)
|
|
|
|
# Swap by moving elem1 forward
|
|
elements = layout.elements
|
|
index1 = elements.index(elem1)
|
|
index2 = elements.index(elem2)
|
|
elements[index1], elements[index2] = elements[index2], elements[index1]
|
|
|
|
assert layout.elements[0] is elem2
|
|
assert layout.elements[1] is elem1
|
|
|
|
def test_multiple_zorder_changes(self):
|
|
"""Test multiple z-order changes in sequence"""
|
|
layout = PageLayout()
|
|
history = CommandHistory()
|
|
|
|
elem1 = ImageData(x=10, y=10)
|
|
elem2 = TextBoxData(x=20, y=20)
|
|
elem3 = PlaceholderData(x=30, y=30)
|
|
|
|
layout.add_element(elem1)
|
|
layout.add_element(elem2)
|
|
layout.add_element(elem3)
|
|
|
|
# Move elem1 to front
|
|
cmd1 = ChangeZOrderCommand(layout, elem1, old_index=0, new_index=2)
|
|
history.execute(cmd1)
|
|
assert layout.elements.index(elem1) == 2
|
|
|
|
# Move elem2 to front
|
|
cmd2 = ChangeZOrderCommand(layout, elem2, old_index=0, new_index=2)
|
|
history.execute(cmd2)
|
|
assert layout.elements.index(elem2) == 2
|
|
|
|
# Undo both
|
|
history.undo()
|
|
assert layout.elements.index(elem2) == 0
|
|
|
|
history.undo()
|
|
assert layout.elements.index(elem1) == 0
|
|
|
|
|
|
class TestZOrderCommandSerialization:
|
|
"""Tests for ChangeZOrderCommand serialization"""
|
|
|
|
def test_serialize_command(self):
|
|
"""Test serializing a ChangeZOrderCommand"""
|
|
layout = PageLayout()
|
|
elem = ImageData(x=10, y=10)
|
|
layout.add_element(elem)
|
|
|
|
cmd = ChangeZOrderCommand(layout, elem, old_index=0, new_index=1)
|
|
|
|
data = cmd.serialize()
|
|
|
|
assert data['type'] == 'change_zorder'
|
|
assert data['old_index'] == 0
|
|
assert data['new_index'] == 1
|
|
assert 'element' in data
|
|
|
|
def test_deserialize_command(self):
|
|
"""Test deserializing a ChangeZOrderCommand"""
|
|
data = {
|
|
'type': 'change_zorder',
|
|
'element': {
|
|
'type': 'image',
|
|
'position': (10, 10),
|
|
'size': (50, 50),
|
|
'rotation': 0,
|
|
'z_index': 0,
|
|
'image_path': '',
|
|
'crop_info': (0, 0, 1, 1)
|
|
},
|
|
'old_index': 0,
|
|
'new_index': 1
|
|
}
|
|
|
|
cmd = ChangeZOrderCommand.deserialize(data, None)
|
|
|
|
assert isinstance(cmd, ChangeZOrderCommand)
|
|
assert cmd.old_index == 0
|
|
assert cmd.new_index == 1
|
|
assert isinstance(cmd.element, ImageData)
|