""" Integration tests for the refactored UndoableInteractionMixin. """ import pytest from unittest.mock import Mock, MagicMock, patch from pyPhotoAlbum.mixins.interaction_undo import UndoableInteractionMixin from pyPhotoAlbum.models import BaseLayoutElement class MockWidget(UndoableInteractionMixin): """Mock widget that uses the UndoableInteractionMixin.""" def __init__(self): # Simulate QWidget initialization self._mock_window = Mock() self._mock_window.project = Mock() self._mock_window.project.history = Mock() super().__init__() def window(self): """Mock window() method.""" return self._mock_window class TestUndoableInteractionMixinRefactored: """Tests for refactored UndoableInteractionMixin.""" def test_initialization(self): """Test that mixin initializes correctly.""" widget = MockWidget() assert hasattr(widget, "_command_factory") assert hasattr(widget, "_interaction_state") def test_begin_move(self): """Test beginning a move interaction.""" widget = MockWidget() element = Mock(spec=BaseLayoutElement) element.position = (0.0, 0.0) widget._begin_move(element) assert widget._interaction_state.element == element assert widget._interaction_state.interaction_type == "move" assert widget._interaction_state.position == (0.0, 0.0) def test_begin_resize(self): """Test beginning a resize interaction.""" widget = MockWidget() element = Mock(spec=BaseLayoutElement) element.position = (0.0, 0.0) element.size = (100.0, 100.0) widget._begin_resize(element) assert widget._interaction_state.element == element assert widget._interaction_state.interaction_type == "resize" assert widget._interaction_state.position == (0.0, 0.0) assert widget._interaction_state.size == (100.0, 100.0) def test_begin_rotate(self): """Test beginning a rotate interaction.""" widget = MockWidget() element = Mock(spec=BaseLayoutElement) element.rotation = 0.0 widget._begin_rotate(element) assert widget._interaction_state.element == element assert widget._interaction_state.interaction_type == "rotate" assert widget._interaction_state.rotation == 0.0 def test_begin_image_pan(self): """Test beginning an image pan interaction.""" from pyPhotoAlbum.models import ImageData widget = MockWidget() element = Mock(spec=ImageData) element.crop_info = (0.0, 0.0, 1.0, 1.0) widget._begin_image_pan(element) assert widget._interaction_state.element == element assert widget._interaction_state.interaction_type == "image_pan" assert widget._interaction_state.crop_info == (0.0, 0.0, 1.0, 1.0) def test_begin_image_pan_non_image_element(self): """Test that image pan doesn't start for non-ImageData elements.""" widget = MockWidget() element = Mock(spec=BaseLayoutElement) widget._begin_image_pan(element) # Should not set interaction state assert widget._interaction_state.element is None def test_end_interaction_creates_move_command(self): """Test that ending a move interaction creates a command.""" widget = MockWidget() element = Mock(spec=BaseLayoutElement) element.position = (0.0, 0.0) widget._begin_move(element) # Simulate movement element.position = (10.0, 10.0) widget._end_interaction() # Verify command was executed widget._mock_window.project.history.execute.assert_called_once() def test_end_interaction_creates_resize_command(self): """Test that ending a resize interaction creates a command.""" widget = MockWidget() element = Mock(spec=BaseLayoutElement) element.position = (0.0, 0.0) element.size = (100.0, 100.0) widget._begin_resize(element) # Simulate resize element.size = (200.0, 200.0) widget._end_interaction() # Verify command was executed widget._mock_window.project.history.execute.assert_called_once() def test_end_interaction_creates_rotate_command(self): """Test that ending a rotate interaction creates a command.""" widget = MockWidget() element = Mock(spec=BaseLayoutElement) element.rotation = 0.0 element.position = (0.0, 0.0) # Required by RotateElementCommand element.size = (100.0, 100.0) # Required by RotateElementCommand widget._begin_rotate(element) # Simulate rotation element.rotation = 45.0 widget._end_interaction() # Verify command was executed widget._mock_window.project.history.execute.assert_called_once() def test_end_interaction_no_command_for_insignificant_change(self): """Test that no command is created for insignificant changes.""" widget = MockWidget() element = Mock(spec=BaseLayoutElement) element.position = (0.0, 0.0) widget._begin_move(element) # Insignificant movement element.position = (0.05, 0.05) widget._end_interaction() # Verify no command was executed widget._mock_window.project.history.execute.assert_not_called() def test_end_interaction_clears_state(self): """Test that ending interaction clears state.""" widget = MockWidget() element = Mock(spec=BaseLayoutElement) element.position = (0.0, 0.0) widget._begin_move(element) widget._end_interaction() assert widget._interaction_state.element is None assert widget._interaction_state.interaction_type is None def test_end_interaction_without_begin(self): """Test that ending interaction without beginning is safe.""" widget = MockWidget() widget._end_interaction() # Should not crash or execute commands widget._mock_window.project.history.execute.assert_not_called() def test_cancel_interaction(self): """Test canceling an interaction.""" widget = MockWidget() element = Mock(spec=BaseLayoutElement) element.position = (0.0, 0.0) widget._begin_move(element) widget._cancel_interaction() assert widget._interaction_state.element is None assert widget._interaction_state.interaction_type is None def test_clear_interaction_state(self): """Test clearing interaction state directly.""" widget = MockWidget() element = Mock(spec=BaseLayoutElement) element.position = (0.0, 0.0) widget._begin_move(element) widget._clear_interaction_state() assert widget._interaction_state.element is None def test_end_interaction_without_project(self): """Test that ending interaction without project is safe.""" widget = MockWidget() # Remove the project attribute entirely delattr(widget._mock_window, "project") element = Mock(spec=BaseLayoutElement) element.position = (0.0, 0.0) widget._begin_move(element) element.position = (10.0, 10.0) widget._end_interaction() # Should clear state without crashing assert widget._interaction_state.element is None class TestMixinIntegrationWithFactory: """Integration tests between mixin and factory.""" def test_move_interaction_complete_flow(self): """Test complete flow of a move interaction.""" widget = MockWidget() element = Mock(spec=BaseLayoutElement) element.position = (0.0, 0.0) # Begin widget._begin_move(element) assert widget._interaction_state.is_valid() # Modify element.position = (50.0, 75.0) # End widget._end_interaction() # Verify widget._mock_window.project.history.execute.assert_called_once() assert not widget._interaction_state.is_valid() def test_resize_interaction_complete_flow(self): """Test complete flow of a resize interaction.""" widget = MockWidget() element = Mock(spec=BaseLayoutElement) element.position = (0.0, 0.0) element.size = (100.0, 100.0) # Begin widget._begin_resize(element) # Modify element.position = (10.0, 10.0) element.size = (200.0, 150.0) # End widget._end_interaction() # Verify widget._mock_window.project.history.execute.assert_called_once() def test_rotate_interaction_complete_flow(self): """Test complete flow of a rotate interaction.""" widget = MockWidget() element = Mock(spec=BaseLayoutElement) element.rotation = 0.0 element.position = (0.0, 0.0) # Required by RotateElementCommand element.size = (100.0, 100.0) # Required by RotateElementCommand # Begin widget._begin_rotate(element) # Modify element.rotation = 90.0 # End widget._end_interaction() # Verify widget._mock_window.project.history.execute.assert_called_once() def test_multiple_interactions_in_sequence(self): """Test multiple interactions in sequence.""" widget = MockWidget() element = Mock(spec=BaseLayoutElement) element.position = (0.0, 0.0) element.size = (100.0, 100.0) element.rotation = 0.0 # First interaction: move widget._begin_move(element) element.position = (10.0, 10.0) widget._end_interaction() # Second interaction: resize widget._begin_resize(element) element.size = (200.0, 200.0) widget._end_interaction() # Third interaction: rotate widget._begin_rotate(element) element.rotation = 45.0 widget._end_interaction() # Should have created 3 commands assert widget._mock_window.project.history.execute.call_count == 3 def test_interaction_with_cancel(self): """Test interaction flow with cancellation.""" widget = MockWidget() element = Mock(spec=BaseLayoutElement) element.position = (0.0, 0.0) # Begin widget._begin_move(element) element.position = (50.0, 50.0) # Cancel instead of end widget._cancel_interaction() # No command should be created widget._mock_window.project.history.execute.assert_not_called()