""" 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)