pyPhotoAlbum/tests/test_alignment_ops_mixin.py
2025-11-11 16:02:02 +00:00

330 lines
11 KiB
Python

"""
Tests for AlignmentOperationsMixin
"""
import pytest
from unittest.mock import Mock, patch
from PyQt6.QtWidgets import QMainWindow
from pyPhotoAlbum.mixins.operations.alignment_ops import AlignmentOperationsMixin
from pyPhotoAlbum.models import ImageData, TextBoxData
from pyPhotoAlbum.project import Project, Page
from pyPhotoAlbum.page_layout import PageLayout
from pyPhotoAlbum.commands import CommandHistory
# Create test window with AlignmentOperationsMixin
class TestAlignmentWindow(AlignmentOperationsMixin, QMainWindow):
"""Test window with alignment operations mixin"""
def __init__(self):
super().__init__()
# Mock GL widget
self.gl_widget = Mock()
self.gl_widget.selected_elements = set()
# Mock project
self.project = Mock()
self.project.history = CommandHistory()
# Track method calls
self._update_view_called = False
self._status_message = None
self._require_selection_count = None
def require_selection(self, min_count=1):
"""Track require_selection calls"""
self._require_selection_count = min_count
return len(self.gl_widget.selected_elements) >= min_count
def update_view(self):
"""Track update_view calls"""
self._update_view_called = True
def show_status(self, message, timeout=0):
"""Track status messages"""
self._status_message = message
class TestGetSelectedElementsList:
"""Test _get_selected_elements_list helper"""
def test_get_selected_elements_list(self, qtbot):
"""Test getting selected elements as list"""
window = TestAlignmentWindow()
qtbot.addWidget(window)
element1 = ImageData(image_path="/test1.jpg", x=0, y=0, width=100, height=100)
element2 = ImageData(image_path="/test2.jpg", x=100, y=100, width=100, height=100)
window.gl_widget.selected_elements = {element1, element2}
result = window._get_selected_elements_list()
assert isinstance(result, list)
assert len(result) == 2
assert element1 in result
assert element2 in result
def test_get_selected_elements_list_empty(self, qtbot):
"""Test getting empty list when nothing selected"""
window = TestAlignmentWindow()
qtbot.addWidget(window)
window.gl_widget.selected_elements = set()
result = window._get_selected_elements_list()
assert result == []
class TestAlignLeft:
"""Test align_left method"""
@patch('pyPhotoAlbum.mixins.operations.alignment_ops.AlignmentManager')
def test_align_left_success(self, mock_manager, qtbot):
"""Test aligning elements to the left"""
window = TestAlignmentWindow()
qtbot.addWidget(window)
element1 = ImageData(image_path="/test1.jpg", x=100, y=0, width=100, height=100)
element2 = ImageData(image_path="/test2.jpg", x=200, y=100, width=100, height=100)
window.gl_widget.selected_elements = {element1, element2}
# Mock AlignmentManager to return changes
mock_manager.align_left.return_value = [
(element1, (100, 0)),
(element2, (200, 100))
]
window.align_left()
# Should call AlignmentManager
assert mock_manager.align_left.called
assert window._update_view_called
assert "aligned" in window._status_message.lower()
assert "left" in window._status_message.lower()
@patch('pyPhotoAlbum.mixins.operations.alignment_ops.AlignmentManager')
def test_align_left_no_changes(self, mock_manager, qtbot):
"""Test align left when no changes needed"""
window = TestAlignmentWindow()
qtbot.addWidget(window)
element1 = ImageData(image_path="/test1.jpg", x=0, y=0, width=100, height=100)
element2 = ImageData(image_path="/test2.jpg", x=0, y=100, width=100, height=100)
window.gl_widget.selected_elements = {element1, element2}
# Mock AlignmentManager to return no changes
mock_manager.align_left.return_value = []
window.align_left()
# Should not update view or show status
assert not window._update_view_called
def test_align_left_insufficient_selection(self, qtbot):
"""Test align left with fewer than 2 elements"""
window = TestAlignmentWindow()
qtbot.addWidget(window)
element1 = ImageData(image_path="/test1.jpg", x=0, y=0, width=100, height=100)
window.gl_widget.selected_elements = {element1}
window.align_left()
# Should check for minimum 2 elements
assert window._require_selection_count == 2
assert not window._update_view_called
class TestAlignRight:
"""Test align_right method"""
@patch('pyPhotoAlbum.mixins.operations.alignment_ops.AlignmentManager')
def test_align_right_success(self, mock_manager, qtbot):
"""Test aligning elements to the right"""
window = TestAlignmentWindow()
qtbot.addWidget(window)
element1 = ImageData(image_path="/test1.jpg", x=100, y=0, width=100, height=100)
element2 = ImageData(image_path="/test2.jpg", x=200, y=100, width=150, height=100)
window.gl_widget.selected_elements = {element1, element2}
mock_manager.align_right.return_value = [
(element1, (100, 0)),
(element2, (200, 100))
]
window.align_right()
assert mock_manager.align_right.called
assert window._update_view_called
assert "right" in window._status_message.lower()
class TestAlignTop:
"""Test align_top method"""
@patch('pyPhotoAlbum.mixins.operations.alignment_ops.AlignmentManager')
def test_align_top_success(self, mock_manager, qtbot):
"""Test aligning elements to the top"""
window = TestAlignmentWindow()
qtbot.addWidget(window)
element1 = ImageData(image_path="/test1.jpg", x=0, y=50, width=100, height=100)
element2 = ImageData(image_path="/test2.jpg", x=100, y=100, width=100, height=100)
window.gl_widget.selected_elements = {element1, element2}
mock_manager.align_top.return_value = [
(element1, (0, 50)),
(element2, (100, 100))
]
window.align_top()
assert mock_manager.align_top.called
assert window._update_view_called
assert "top" in window._status_message.lower()
class TestAlignBottom:
"""Test align_bottom method"""
@patch('pyPhotoAlbum.mixins.operations.alignment_ops.AlignmentManager')
def test_align_bottom_success(self, mock_manager, qtbot):
"""Test aligning elements to the bottom"""
window = TestAlignmentWindow()
qtbot.addWidget(window)
element1 = ImageData(image_path="/test1.jpg", x=0, y=50, width=100, height=100)
element2 = ImageData(image_path="/test2.jpg", x=100, y=100, width=100, height=150)
window.gl_widget.selected_elements = {element1, element2}
mock_manager.align_bottom.return_value = [
(element1, (0, 50)),
(element2, (100, 100))
]
window.align_bottom()
assert mock_manager.align_bottom.called
assert window._update_view_called
assert "bottom" in window._status_message.lower()
class TestAlignHorizontalCenter:
"""Test align_horizontal_center method"""
@patch('pyPhotoAlbum.mixins.operations.alignment_ops.AlignmentManager')
def test_align_horizontal_center_success(self, mock_manager, qtbot):
"""Test aligning elements to horizontal center"""
window = TestAlignmentWindow()
qtbot.addWidget(window)
element1 = ImageData(image_path="/test1.jpg", x=0, y=0, width=100, height=100)
element2 = ImageData(image_path="/test2.jpg", x=200, y=100, width=100, height=100)
window.gl_widget.selected_elements = {element1, element2}
mock_manager.align_horizontal_center.return_value = [
(element1, (0, 0)),
(element2, (200, 100))
]
window.align_horizontal_center()
assert mock_manager.align_horizontal_center.called
assert window._update_view_called
assert "horizontal center" in window._status_message.lower()
class TestAlignVerticalCenter:
"""Test align_vertical_center method"""
@patch('pyPhotoAlbum.mixins.operations.alignment_ops.AlignmentManager')
def test_align_vertical_center_success(self, mock_manager, qtbot):
"""Test aligning elements to vertical center"""
window = TestAlignmentWindow()
qtbot.addWidget(window)
element1 = ImageData(image_path="/test1.jpg", x=0, y=0, width=100, height=100)
element2 = ImageData(image_path="/test2.jpg", x=100, y=200, width=100, height=100)
window.gl_widget.selected_elements = {element1, element2}
mock_manager.align_vertical_center.return_value = [
(element1, (0, 0)),
(element2, (100, 200))
]
window.align_vertical_center()
assert mock_manager.align_vertical_center.called
assert window._update_view_called
assert "vertical center" in window._status_message.lower()
class TestAlignmentCommandPattern:
"""Test alignment operations with command pattern for undo/redo"""
@patch('pyPhotoAlbum.mixins.operations.alignment_ops.AlignmentManager')
def test_alignment_creates_command(self, mock_manager, qtbot):
"""Test that alignment creates a command for undo"""
window = TestAlignmentWindow()
qtbot.addWidget(window)
element1 = ImageData(image_path="/test1.jpg", x=100, y=0, width=100, height=100)
element2 = ImageData(image_path="/test2.jpg", x=200, y=100, width=100, height=100)
window.gl_widget.selected_elements = {element1, element2}
mock_manager.align_left.return_value = [
(element1, (100, 0)),
(element2, (200, 100))
]
# Should have no commands initially
assert not window.project.history.can_undo()
window.align_left()
# Should have created a command
assert window.project.history.can_undo()
@patch('pyPhotoAlbum.mixins.operations.alignment_ops.AlignmentManager')
def test_alignment_undo_redo(self, mock_manager, qtbot):
"""Test that alignment can be undone and redone"""
window = TestAlignmentWindow()
qtbot.addWidget(window)
element1 = ImageData(image_path="/test1.jpg", x=100, y=0, width=100, height=100)
element2 = ImageData(image_path="/test2.jpg", x=200, y=100, width=100, height=100)
window.gl_widget.selected_elements = {element1, element2}
# Mock alignment to return changes (command will handle actual moves)
mock_manager.align_top.return_value = [
(element1, (100, 0)),
(element2, (200, 100))
]
# Execute alignment - command created
window.align_top()
# Should have created a command
assert window.project.history.can_undo()
# Can redo after undo
window.project.history.undo()
assert window.project.history.can_redo()
# Redo works
window.project.history.redo()
assert not window.project.history.can_redo() # Nothing left to redo