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