All checks were successful
Python CI / test (push) Successful in 1m20s
Lint / lint (push) Successful in 1m4s
Tests / test (3.11) (push) Successful in 1m27s
Tests / test (3.12) (push) Successful in 2m25s
Tests / test (3.13) (push) Successful in 2m52s
Tests / test (3.14) (push) Successful in 1m9s
403 lines
12 KiB
Python
Executable File
403 lines
12 KiB
Python
Executable File
"""
|
|
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)
|