""" Tests for MouseInteractionMixin """ import pytest from unittest.mock import Mock, MagicMock, patch from PyQt6.QtCore import Qt, QPoint, QPointF from PyQt6.QtGui import QMouseEvent, QWheelEvent from PyQt6.QtOpenGLWidgets import QOpenGLWidget from pyPhotoAlbum.mixins.mouse_interaction import MouseInteractionMixin from pyPhotoAlbum.mixins.viewport import ViewportMixin from pyPhotoAlbum.mixins.element_selection import ElementSelectionMixin from pyPhotoAlbum.mixins.element_manipulation import ElementManipulationMixin from pyPhotoAlbum.mixins.image_pan import ImagePanMixin from pyPhotoAlbum.mixins.page_navigation import PageNavigationMixin from pyPhotoAlbum.mixins.interaction_undo import UndoableInteractionMixin from pyPhotoAlbum.project import Project, Page from pyPhotoAlbum.page_layout import PageLayout from pyPhotoAlbum.models import ImageData, TextBoxData # Create test widget combining necessary mixins class TestMouseInteractionWidget( MouseInteractionMixin, PageNavigationMixin, ImagePanMixin, ElementManipulationMixin, ElementSelectionMixin, ViewportMixin, UndoableInteractionMixin, QOpenGLWidget, ): """Test widget combining mouse interaction with other required mixins""" def __init__(self): super().__init__() # Initialize additional state not covered by mixins self.current_page_index = 0 self.resize_handle = None self.rotation_snap_angle = 15 class TestMouseInteractionInitialization: """Test MouseInteractionMixin initialization""" def test_widget_initializes_state(self, qtbot): """Test that widget initializes mouse interaction state""" widget = TestMouseInteractionWidget() qtbot.addWidget(widget) # Should have initialized state assert hasattr(widget, "drag_start_pos") assert hasattr(widget, "is_dragging") assert hasattr(widget, "is_panning") assert widget.drag_start_pos is None assert widget.is_dragging is False assert widget.is_panning is False class TestMousePressEvent: """Test mousePressEvent method""" def test_left_click_starts_drag(self, qtbot): """Test left click starts drag operation - clears selection on empty click""" widget = TestMouseInteractionWidget() qtbot.addWidget(widget) # Mock update method widget.update = Mock() # Create left click event event = Mock() event.button = Mock(return_value=Qt.MouseButton.LeftButton) event.position = Mock(return_value=QPointF(100, 100)) event.modifiers = Mock(return_value=Qt.KeyboardModifier.NoModifier) # Mock element selection and ghost page check widget._get_element_at = Mock(return_value=None) widget._get_page_at = Mock(return_value=(None, -1, None)) widget._check_ghost_page_click = Mock(return_value=False) widget.mousePressEvent(event) # Should clear selection when clicking on empty space assert len(widget.selected_elements) == 0 assert widget.update.called def test_left_click_selects_element(self, qtbot): """Test left click on element selects it""" widget = TestMouseInteractionWidget() qtbot.addWidget(widget) widget.update = Mock() # Create mock element mock_element = ImageData(image_path="/test.jpg", x=50, y=50, width=100, height=100) event = Mock() event.button = Mock(return_value=Qt.MouseButton.LeftButton) event.position = Mock(return_value=QPointF(75, 75)) event.modifiers = Mock(return_value=Qt.KeyboardModifier.NoModifier) # Mock element selection to return the element widget._get_element_at = Mock(return_value=mock_element) widget._get_page_at = Mock(return_value=(None, -1, None)) widget._check_ghost_page_click = Mock(return_value=False) widget.mousePressEvent(event) # Should select the element assert mock_element in widget.selected_elements assert widget.drag_start_pos == (75, 75) assert widget.is_dragging is True def test_ctrl_click_image_enters_image_pan_mode(self, qtbot): """Test Ctrl+click on ImageData enters image pan mode""" widget = TestMouseInteractionWidget() qtbot.addWidget(widget) widget.update = Mock() widget.setCursor = Mock() # Create image element with crop info element = ImageData( image_path="/test.jpg", x=50, y=50, width=100, height=100, crop_info=(0.0, 0.0, 1.0, 1.0), # crop_info is a tuple (x, y, width, height) ) event = Mock() event.button = Mock(return_value=Qt.MouseButton.LeftButton) event.position = Mock(return_value=QPointF(75, 75)) event.modifiers = Mock(return_value=Qt.KeyboardModifier.ControlModifier) # Mock element selection to return the image element widget._get_element_at = Mock(return_value=element) widget._get_page_at = Mock(return_value=(None, -1, None)) widget._check_ghost_page_click = Mock(return_value=False) widget.mousePressEvent(event) # Should enter image pan mode assert element in widget.selected_elements assert widget.image_pan_mode is True assert widget.is_dragging is True assert widget.setCursor.called def test_middle_click_starts_panning(self, qtbot): """Test middle mouse button starts panning""" widget = TestMouseInteractionWidget() qtbot.addWidget(widget) widget.update = Mock() event = Mock() event.button = Mock(return_value=Qt.MouseButton.MiddleButton) event.position = Mock(return_value=QPointF(150, 150)) widget.mousePressEvent(event) # Should start panning assert widget.is_panning is True assert widget.drag_start_pos == (150, 150) def test_click_on_ghost_page_adds_page(self, qtbot): """Test clicking on ghost page calls check method""" widget = TestMouseInteractionWidget() qtbot.addWidget(widget) widget.update = Mock() # Mock ghost page click check to return True (handled ghost click) widget._check_ghost_page_click = Mock(return_value=True) event = Mock() event.button = Mock(return_value=Qt.MouseButton.LeftButton) event.position = Mock(return_value=QPointF(100, 100)) event.modifiers = Mock(return_value=Qt.KeyboardModifier.NoModifier) widget.mousePressEvent(event) # Should have called check_ghost_page_click assert widget._check_ghost_page_click.called # Ghost click returns early, so update should not be called assert not widget.update.called class TestMouseMoveEvent: """Test mouseMoveEvent method""" def test_hover_shows_resize_cursor(self, qtbot): """Test hovering over resize handle (not dragging)""" widget = TestMouseInteractionWidget() qtbot.addWidget(widget) widget.update = Mock() widget._update_page_status = Mock() # Create selected element element = ImageData(image_path="/test.jpg", x=50, y=50, width=100, height=100) widget.selected_elements.add(element) event = Mock() event.position = Mock(return_value=QPointF(150, 150)) # Bottom-right corner event.buttons = Mock(return_value=Qt.MouseButton.NoButton) # Mock resize handle detection widget._get_resize_handle_at = Mock(return_value="bottom-right") widget._get_element_at = Mock(return_value=element) widget.mouseMoveEvent(event) # Should call _update_page_status but not update (no drag) assert widget._update_page_status.called # No dragging, so no update call assert not widget.update.called def test_drag_moves_element(self, qtbot): """Test dragging moves selected element""" widget = TestMouseInteractionWidget() qtbot.addWidget(widget) widget.update = Mock() widget._update_page_status = Mock() # Setup project mock_window = Mock() mock_window.project = Project(name="Test") mock_window.project.working_dpi = 96 page = Page(layout=PageLayout(width=210, height=297), page_number=1) mock_window.project.pages = [page] widget.window = Mock(return_value=mock_window) # Create and select element element = ImageData(image_path="/test.jpg", x=100, y=100, width=100, height=100) page.layout.add_element(element) widget.selected_elements.add(element) element._parent_page = page # Start drag widget.drag_start_pos = (150, 150) widget.is_dragging = True widget.drag_start_element_pos = (100, 100) # Mock page detection mock_renderer = Mock() mock_renderer.screen_to_page = Mock(return_value=(180, 180)) widget._get_page_at = Mock(return_value=(page, 0, mock_renderer)) event = Mock() event.position = Mock(return_value=QPointF(180, 180)) event.buttons = Mock(return_value=Qt.MouseButton.LeftButton) event.modifiers = Mock(return_value=Qt.KeyboardModifier.NoModifier) widget.mouseMoveEvent(event) # Element should have moved (with snapping, so position should change) assert element.position != (100, 100) assert widget.update.called def test_middle_button_panning(self, qtbot): """Test middle button drag pans viewport""" widget = TestMouseInteractionWidget() qtbot.addWidget(widget) widget.update = Mock() widget.clamp_pan_offset = Mock() # Mock clamping to allow any pan offset # Start panning widget.is_panning = True widget.drag_start_pos = (100, 100) initial_pan = widget.pan_offset.copy() event = Mock() event.position = Mock(return_value=QPointF(150, 150)) event.buttons = Mock(return_value=Qt.MouseButton.MiddleButton) widget.mouseMoveEvent(event) # Pan offset should have changed assert widget.pan_offset != initial_pan assert widget.pan_offset == [50.0, 50.0] # Moved by 50 pixels in each direction assert widget.update.called assert widget.clamp_pan_offset.called # Clamping should be called def test_ctrl_drag_pans_image_in_frame(self, qtbot): """Test Ctrl+drag pans image within frame""" widget = TestMouseInteractionWidget() qtbot.addWidget(widget) widget.update = Mock() widget._update_page_status = Mock() # Create image element with crop info element = ImageData( image_path="/test.jpg", x=100, y=100, width=100, height=100, crop_info=(0.0, 0.0, 1.0, 1.0), # crop_info is a tuple (x, y, width, height) ) widget.selected_elements.add(element) # Start image pan drag widget.drag_start_pos = (150, 150) widget.is_dragging = True widget.image_pan_mode = True widget._image_pan_start = (0.0, 0.0) event = Mock() event.position = Mock(return_value=QPointF(160, 160)) event.buttons = Mock(return_value=Qt.MouseButton.LeftButton) event.modifiers = Mock(return_value=Qt.KeyboardModifier.ControlModifier) # Mock _handle_image_pan_move method widget._handle_image_pan_move = Mock() widget.mouseMoveEvent(event) # Should call _handle_image_pan_move assert widget._handle_image_pan_move.called class TestMouseReleaseEvent: """Test mouseReleaseEvent method""" def test_release_clears_drag_state(self, qtbot): """Test mouse release clears drag state""" widget = TestMouseInteractionWidget() qtbot.addWidget(widget) widget.setCursor = Mock() # Setup drag state widget.is_dragging = True widget.drag_start_pos = (100, 100) event = Mock() event.button = Mock(return_value=Qt.MouseButton.LeftButton) widget.mouseReleaseEvent(event) # Should clear drag state assert widget.is_dragging is False assert widget.drag_start_pos is None assert widget.setCursor.called def test_release_clears_panning_state(self, qtbot): """Test mouse release clears panning state""" widget = TestMouseInteractionWidget() qtbot.addWidget(widget) widget.update = Mock() # Setup panning state widget.is_panning = True widget.drag_start_pos = (100, 100) event = Mock() event.button = Mock(return_value=Qt.MouseButton.MiddleButton) widget.mouseReleaseEvent(event) # Should clear panning state assert widget.is_panning is False assert widget.drag_start_pos is None class TestMouseDoubleClickEvent: """Test mouseDoubleClickEvent method""" def test_double_click_text_starts_editing(self, qtbot): """Test double-clicking text element starts editing""" widget = TestMouseInteractionWidget() qtbot.addWidget(widget) # Create text element with correct constructor text_element = TextBoxData( text_content="Test", x=100, y=100, width=100, height=50, font_settings={"family": "Arial", "size": 12, "color": (0, 0, 0)}, ) # Mock _edit_text_element method widget._edit_text_element = Mock() # Mock element selection to return text element widget._get_element_at = Mock(return_value=text_element) # Create real event (not Mock) for button() event = QMouseEvent( QMouseEvent.Type.MouseButtonDblClick, QPointF(125, 125), Qt.MouseButton.LeftButton, Qt.MouseButton.LeftButton, Qt.KeyboardModifier.NoModifier, ) widget.mouseDoubleClickEvent(event) # Should call _edit_text_element widget._edit_text_element.assert_called_once_with(text_element) def test_double_click_non_text_does_nothing(self, qtbot): """Test double-clicking non-text element does nothing""" widget = TestMouseInteractionWidget() qtbot.addWidget(widget) # Create image element (not text) element = ImageData(image_path="/test.jpg", x=100, y=100, width=100, height=100) widget._edit_text_element = Mock() widget._get_element_at = Mock(return_value=element) # Create real event (not Mock) for button() event = QMouseEvent( QMouseEvent.Type.MouseButtonDblClick, QPointF(125, 125), Qt.MouseButton.LeftButton, Qt.MouseButton.LeftButton, Qt.KeyboardModifier.NoModifier, ) widget.mouseDoubleClickEvent(event) # Should not call _edit_text_element assert not widget._edit_text_element.called class TestWheelEvent: """Test wheelEvent method""" def test_scroll_pans_viewport(self, qtbot): """Test scroll wheel pans viewport without Ctrl""" widget = TestMouseInteractionWidget() qtbot.addWidget(widget) widget.update = Mock() initial_pan = widget.pan_offset[1] event = Mock() event.angleDelta = Mock(return_value=Mock(x=Mock(return_value=0), y=Mock(return_value=-120))) # Scroll down event.modifiers = Mock(return_value=Qt.KeyboardModifier.NoModifier) widget.wheelEvent(event) # Should pan viewport assert widget.pan_offset[1] != initial_pan assert widget.update.called def test_ctrl_scroll_zooms(self, qtbot): """Test Ctrl+scroll zooms viewport""" widget = TestMouseInteractionWidget() qtbot.addWidget(widget) widget.update = Mock() initial_zoom = widget.zoom_level event = Mock() event.angleDelta = Mock(return_value=Mock(y=Mock(return_value=120))) # Scroll up event.modifiers = Mock(return_value=Qt.KeyboardModifier.ControlModifier) event.position = Mock(return_value=QPointF(100, 100)) widget.wheelEvent(event) # Should zoom in assert widget.zoom_level > initial_zoom assert widget.update.called def test_scroll_up_pans_viewport_up(self, qtbot): """Test scrolling up pans viewport upward""" widget = TestMouseInteractionWidget() qtbot.addWidget(widget) widget.update = Mock() # Mock clamp_pan_offset to prevent it from resetting pan_offset widget.clamp_pan_offset = Mock() initial_pan = widget.pan_offset[1] event = Mock() event.angleDelta = Mock(return_value=Mock(x=Mock(return_value=0), y=Mock(return_value=120))) # Scroll up event.modifiers = Mock(return_value=Qt.KeyboardModifier.NoModifier) widget.wheelEvent(event) # Should pan viewport up (increase pan_offset[1]) assert widget.pan_offset[1] > initial_pan assert widget.update.called def test_scroll_down_pans_viewport_down(self, qtbot): """Test scrolling down pans viewport downward""" widget = TestMouseInteractionWidget() qtbot.addWidget(widget) widget.update = Mock() # Mock clamp_pan_offset to prevent it from resetting pan_offset widget.clamp_pan_offset = Mock() initial_pan = widget.pan_offset[1] event = Mock() event.angleDelta = Mock(return_value=Mock(x=Mock(return_value=0), y=Mock(return_value=-120))) # Scroll down event.modifiers = Mock(return_value=Qt.KeyboardModifier.NoModifier) widget.wheelEvent(event) # Should pan viewport down (decrease pan_offset[1]) assert widget.pan_offset[1] < initial_pan assert widget.update.called class TestRotationMode: """Test rotation mode functionality""" def test_click_in_rotation_mode_starts_rotation(self, qtbot): """Test clicking on element in rotation mode starts rotation""" widget = TestMouseInteractionWidget() qtbot.addWidget(widget) widget.update = Mock() widget.rotation_mode = True # Create and select element element = ImageData(image_path="/test.jpg", x=100, y=100, width=100, height=100) widget.selected_elements.add(element) # Mock the _begin_rotate method widget._begin_rotate = Mock() event = Mock() event.button = Mock(return_value=Qt.MouseButton.LeftButton) event.position = Mock(return_value=QPointF(150, 150)) event.modifiers = Mock(return_value=Qt.KeyboardModifier.NoModifier) widget._get_element_at = Mock(return_value=element) widget._get_page_at = Mock(return_value=(None, -1, None)) widget._check_ghost_page_click = Mock(return_value=False) widget.mousePressEvent(event) # Should start rotation assert widget.is_dragging is True assert widget.drag_start_pos == (150, 150) assert hasattr(widget, "rotation_start_angle") widget._begin_rotate.assert_called_once_with(element) def test_mouse_move_in_rotation_mode(self, qtbot): """Test mouse move in rotation mode rotates element""" widget = TestMouseInteractionWidget() qtbot.addWidget(widget) widget.update = Mock() widget._update_page_status = Mock() widget.rotation_mode = True # Create element with page renderer element = ImageData(image_path="/test.jpg", x=100, y=100, width=100, height=100) element.rotation = 0 widget.selected_elements.add(element) # Create mock renderer mock_renderer = Mock() mock_renderer.page_to_screen = Mock(return_value=(150, 150)) # Center of element element._page_renderer = mock_renderer # Start dragging in rotation mode widget.drag_start_pos = (150, 150) widget.is_dragging = True widget.rotation_start_angle = 0 # Mock window with show_status mock_window = Mock() mock_window.show_status = Mock() widget.window = Mock(return_value=mock_window) event = Mock() event.position = Mock(return_value=QPointF(200, 150)) # Mouse to the right widget.mouseMoveEvent(event) # Rotation should be updated (0 degrees for right side) assert element.rotation == 0 # Snapped to nearest 15 degrees assert widget.update.called mock_window.show_status.assert_called() class TestResizeMode: """Test resize mode functionality""" def test_click_on_resize_handle_starts_resize(self, qtbot): """Test clicking on resize handle starts resize""" widget = TestMouseInteractionWidget() qtbot.addWidget(widget) widget.update = Mock() widget.rotation_mode = False # Create and select element element = ImageData(image_path="/test.jpg", x=100, y=100, width=100, height=100) widget.selected_elements.add(element) # Mock the _begin_resize method and _get_resize_handle_at widget._begin_resize = Mock() widget._get_resize_handle_at = Mock(return_value="bottom-right") event = Mock() event.button = Mock(return_value=Qt.MouseButton.LeftButton) event.position = Mock(return_value=QPointF(200, 200)) # Bottom-right corner event.modifiers = Mock(return_value=Qt.KeyboardModifier.NoModifier) widget._get_element_at = Mock(return_value=element) widget._get_page_at = Mock(return_value=(None, -1, None)) widget._check_ghost_page_click = Mock(return_value=False) widget.mousePressEvent(event) # Should start resize assert widget.is_dragging is True assert widget.resize_handle == "bottom-right" assert widget.drag_start_pos == (200, 200) widget._begin_resize.assert_called_once_with(element) def test_mouse_move_in_resize_mode(self, qtbot): """Test mouse move in resize mode resizes element""" widget = TestMouseInteractionWidget() qtbot.addWidget(widget) widget.update = Mock() widget._update_page_status = Mock() # Create and select element element = ImageData(image_path="/test.jpg", x=100, y=100, width=100, height=100) widget.selected_elements.add(element) # Start dragging in resize mode widget.drag_start_pos = (200, 200) widget.is_dragging = True widget.resize_handle = "bottom-right" widget.resize_start_pos = (100, 100) widget.resize_start_size = (100, 100) # Mock _resize_element widget._resize_element = Mock() event = Mock() event.position = Mock(return_value=QPointF(220, 220)) widget.mouseMoveEvent(event) # Should call _resize_element with deltas widget._resize_element.assert_called_once() assert widget.update.called class TestMultiSelect: """Test multi-select functionality""" def test_ctrl_click_non_image_adds_to_selection(self, qtbot): """Test Ctrl+click on non-ImageData adds to multi-selection""" widget = TestMouseInteractionWidget() qtbot.addWidget(widget) widget.update = Mock() # Create and add first element element1 = TextBoxData( text_content="Test 1", x=100, y=100, width=100, height=50, font_settings={"family": "Arial", "size": 12, "color": (0, 0, 0)}, ) widget.selected_elements.add(element1) # Create second element element2 = TextBoxData( text_content="Test 2", x=250, y=100, width=100, height=50, font_settings={"family": "Arial", "size": 12, "color": (0, 0, 0)}, ) event = Mock() event.button = Mock(return_value=Qt.MouseButton.LeftButton) event.position = Mock(return_value=QPointF(300, 125)) event.modifiers = Mock(return_value=Qt.KeyboardModifier.ControlModifier) widget._get_element_at = Mock(return_value=element2) widget._get_page_at = Mock(return_value=(None, -1, None)) widget._check_ghost_page_click = Mock(return_value=False) widget.mousePressEvent(event) # Should add to selection assert element1 in widget.selected_elements assert element2 in widget.selected_elements assert len(widget.selected_elements) == 2 def test_ctrl_click_selected_element_removes_from_selection(self, qtbot): """Test Ctrl+click on already selected element removes it""" widget = TestMouseInteractionWidget() qtbot.addWidget(widget) widget.update = Mock() # Create and select two elements element1 = TextBoxData( text_content="Test 1", x=100, y=100, width=100, height=50, font_settings={"family": "Arial", "size": 12, "color": (0, 0, 0)}, ) element2 = TextBoxData( text_content="Test 2", x=250, y=100, width=100, height=50, font_settings={"family": "Arial", "size": 12, "color": (0, 0, 0)}, ) widget.selected_elements.add(element1) widget.selected_elements.add(element2) event = Mock() event.button = Mock(return_value=Qt.MouseButton.LeftButton) event.position = Mock(return_value=QPointF(150, 125)) event.modifiers = Mock(return_value=Qt.KeyboardModifier.ControlModifier) widget._get_element_at = Mock(return_value=element1) widget._get_page_at = Mock(return_value=(None, -1, None)) widget._check_ghost_page_click = Mock(return_value=False) widget.mousePressEvent(event) # Should remove from selection assert element1 not in widget.selected_elements assert element2 in widget.selected_elements assert len(widget.selected_elements) == 1 def test_shift_click_adds_to_selection(self, qtbot): """Test Shift+click adds element to selection""" widget = TestMouseInteractionWidget() qtbot.addWidget(widget) widget.update = Mock() # Create and select first element element1 = ImageData(image_path="/test1.jpg", x=100, y=100, width=100, height=100) widget.selected_elements.add(element1) # Create second element element2 = ImageData(image_path="/test2.jpg", x=250, y=100, width=100, height=100) event = Mock() event.button = Mock(return_value=Qt.MouseButton.LeftButton) event.position = Mock(return_value=QPointF(300, 150)) event.modifiers = Mock(return_value=Qt.KeyboardModifier.ShiftModifier) widget._get_element_at = Mock(return_value=element2) widget._get_page_at = Mock(return_value=(None, -1, None)) widget._check_ghost_page_click = Mock(return_value=False) widget.mousePressEvent(event) # Should add to selection assert element1 in widget.selected_elements assert element2 in widget.selected_elements assert len(widget.selected_elements) == 2 def test_shift_click_selected_element_removes_it(self, qtbot): """Test Shift+click on selected element removes it""" widget = TestMouseInteractionWidget() qtbot.addWidget(widget) widget.update = Mock() # Create and select two elements element1 = ImageData(image_path="/test1.jpg", x=100, y=100, width=100, height=100) element2 = ImageData(image_path="/test2.jpg", x=250, y=100, width=100, height=100) widget.selected_elements.add(element1) widget.selected_elements.add(element2) event = Mock() event.button = Mock(return_value=Qt.MouseButton.LeftButton) event.position = Mock(return_value=QPointF(150, 150)) event.modifiers = Mock(return_value=Qt.KeyboardModifier.ShiftModifier) widget._get_element_at = Mock(return_value=element1) widget._get_page_at = Mock(return_value=(None, -1, None)) widget._check_ghost_page_click = Mock(return_value=False) widget.mousePressEvent(event) # Should remove from selection assert element1 not in widget.selected_elements assert element2 in widget.selected_elements assert len(widget.selected_elements) == 1 class TestElementPositioningWithoutParentPage: """Test element positioning when element has no parent page""" def test_drag_element_without_parent_page(self, qtbot): """Test dragging element that has no _parent_page attribute""" widget = TestMouseInteractionWidget() qtbot.addWidget(widget) widget.update = Mock() widget._update_page_status = Mock() # Create element without _parent_page element = ImageData(image_path="/test.jpg", x=100, y=100, width=100, height=100) widget.selected_elements.add(element) # Start dragging widget.drag_start_pos = (150, 150) widget.is_dragging = True widget.drag_start_element_pos = (100, 100) # Mock page detection to return a page mock_page = Mock() mock_renderer = Mock() widget._get_page_at = Mock(return_value=(mock_page, 0, mock_renderer)) event = Mock() event.position = Mock(return_value=QPointF(180, 180)) widget.mouseMoveEvent(event) # Element position should be updated (without snapping since no parent page) assert element.position == (130, 130) # Moved by 30 pixels / zoom_level (1.0) assert widget.update.called class TestWheelEventWhileDragging: """Test wheel events during drag operations""" def test_ctrl_scroll_while_dragging_adjusts_drag_start_pos(self, qtbot): """Test Ctrl+scroll while dragging adjusts drag_start_pos for zoom""" widget = TestMouseInteractionWidget() qtbot.addWidget(widget) widget.update = Mock() widget.clamp_pan_offset = Mock() # Setup dragging state widget.is_dragging = True widget.drag_start_pos = (100, 100) event = Mock() event.angleDelta = Mock(return_value=Mock(y=Mock(return_value=120))) # Zoom in event.modifiers = Mock(return_value=Qt.KeyboardModifier.ControlModifier) event.position = Mock(return_value=QPointF(150, 150)) old_drag_pos = widget.drag_start_pos widget.wheelEvent(event) # drag_start_pos should be adjusted assert widget.drag_start_pos != old_drag_pos assert widget.is_dragging is True def test_scroll_while_dragging_adjusts_drag_start_pos(self, qtbot): """Test scrolling while dragging adjusts drag_start_pos for pan""" widget = TestMouseInteractionWidget() qtbot.addWidget(widget) widget.update = Mock() widget.clamp_pan_offset = Mock() # Setup dragging state widget.is_dragging = True widget.drag_start_pos = (100, 100) initial_drag_y = widget.drag_start_pos[1] event = Mock() event.angleDelta = Mock(return_value=Mock(x=Mock(return_value=0), y=Mock(return_value=120))) # Scroll up event.modifiers = Mock(return_value=Qt.KeyboardModifier.NoModifier) widget.wheelEvent(event) # drag_start_pos y should be adjusted for pan assert widget.drag_start_pos[1] != initial_drag_y assert widget.is_dragging is True class TestEditTextElement: """Test _edit_text_element dialog functionality""" def test_edit_text_element_accepted(self, qtbot): """Test editing text element when dialog is accepted""" widget = TestMouseInteractionWidget() qtbot.addWidget(widget) widget.update = Mock() # Create text element text_element = TextBoxData( text_content="Original", x=100, y=100, width=100, height=50, font_settings={"family": "Arial", "size": 12, "color": (0, 0, 0)}, alignment="left", ) # Mock TextEditDialog - patch where it's imported with patch("pyPhotoAlbum.text_edit_dialog.TextEditDialog") as MockDialog: # Create mock instance mock_instance = Mock() MockDialog.return_value = mock_instance # Mock DialogCode.Accepted (needed for comparison in code) mock_dialog_code = Mock() mock_dialog_code.Accepted = 1 MockDialog.DialogCode = mock_dialog_code # Set up the dialog to return Accepted (1) mock_instance.exec.return_value = 1 mock_instance.get_values.return_value = { "text_content": "Updated", "font_settings": {"family": "Helvetica", "size": 14, "color": (0, 0, 0)}, "alignment": "center", } widget._edit_text_element(text_element) # Verify dialog was created and methods called MockDialog.assert_called_once_with(text_element, widget) mock_instance.exec.assert_called_once() mock_instance.get_values.assert_called_once() # Should update element assert text_element.text_content == "Updated" assert text_element.font_settings["family"] == "Helvetica" assert text_element.alignment == "center" assert widget.update.called def test_edit_text_element_rejected(self, qtbot): """Test editing text element when dialog is rejected""" widget = TestMouseInteractionWidget() qtbot.addWidget(widget) widget.update = Mock() # Create text element text_element = TextBoxData( text_content="Original", x=100, y=100, width=100, height=50, font_settings={"family": "Arial", "size": 12, "color": (0, 0, 0)}, alignment="left", ) original_content = text_element.text_content # Mock TextEditDialog with patch("pyPhotoAlbum.text_edit_dialog.TextEditDialog") as MockDialog: mock_dialog = MockDialog.return_value mock_dialog.exec = Mock(return_value=0) # Rejected widget._edit_text_element(text_element) # Should not update element assert text_element.text_content == original_content assert not widget.update.called