""" Tests for AssetDropMixin """ import pytest from unittest.mock import Mock, MagicMock, patch from PyQt6.QtCore import QMimeData, QUrl, QPoint from PyQt6.QtGui import QDragEnterEvent, QDragMoveEvent, QDropEvent from PyQt6.QtOpenGLWidgets import QOpenGLWidget from pyPhotoAlbum.mixins.asset_drop import AssetDropMixin from pyPhotoAlbum.mixins.viewport import ViewportMixin from pyPhotoAlbum.mixins.page_navigation import PageNavigationMixin from pyPhotoAlbum.project import Project, Page from pyPhotoAlbum.page_layout import PageLayout from pyPhotoAlbum.models import ImageData # Create test widget combining necessary mixins class TestAssetDropWidget(AssetDropMixin, PageNavigationMixin, ViewportMixin, QOpenGLWidget): """Test widget combining asset drop, page navigation, and viewport mixins""" def _get_element_at(self, x, y): """Mock implementation for testing""" # Will be overridden in tests that need it return None class TestAssetDropInitialization: """Test AssetDropMixin initialization""" def test_widget_accepts_drops(self, qtbot): """Test that widget is configured to accept drops""" widget = TestAssetDropWidget() qtbot.addWidget(widget) # Should accept drops (set in GLWidget.__init__) # This is a property of the widget, not the mixin assert hasattr(widget, 'acceptDrops') class TestDragEnterEvent: """Test dragEnterEvent method""" def test_accepts_image_urls(self, qtbot): """Test accepts drag events with image file URLs""" widget = TestAssetDropWidget() qtbot.addWidget(widget) # Create mime data with image file mime_data = QMimeData() mime_data.setUrls([QUrl.fromLocalFile("/path/to/image.jpg")]) # Create drag enter event event = Mock() event.mimeData = Mock(return_value=mime_data) event.acceptProposedAction = Mock() widget.dragEnterEvent(event) # Should accept the event assert event.acceptProposedAction.called def test_accepts_png_files(self, qtbot): """Test accepts PNG files""" widget = TestAssetDropWidget() qtbot.addWidget(widget) mime_data = QMimeData() mime_data.setUrls([QUrl.fromLocalFile("/path/to/image.png")]) event = Mock() event.mimeData = Mock(return_value=mime_data) event.acceptProposedAction = Mock() widget.dragEnterEvent(event) assert event.acceptProposedAction.called def test_rejects_non_image_files(self, qtbot): """Test rejects non-image files""" widget = TestAssetDropWidget() qtbot.addWidget(widget) mime_data = QMimeData() mime_data.setUrls([QUrl.fromLocalFile("/path/to/document.pdf")]) event = Mock() event.mimeData = Mock(return_value=mime_data) event.acceptProposedAction = Mock() event.ignore = Mock() widget.dragEnterEvent(event) # Should not accept PDF files assert not event.acceptProposedAction.called def test_rejects_empty_mime_data(self, qtbot): """Test rejects events with no URLs""" widget = TestAssetDropWidget() qtbot.addWidget(widget) mime_data = QMimeData() # No URLs set event = Mock() event.mimeData = Mock(return_value=mime_data) event.acceptProposedAction = Mock() widget.dragEnterEvent(event) assert not event.acceptProposedAction.called class TestDragMoveEvent: """Test dragMoveEvent method""" def test_accepts_drag_move_with_image(self, qtbot): """Test accepts drag move events with image files""" widget = TestAssetDropWidget() qtbot.addWidget(widget) mime_data = QMimeData() mime_data.setUrls([QUrl.fromLocalFile("/path/to/image.jpg")]) event = Mock() event.mimeData = Mock(return_value=mime_data) event.acceptProposedAction = Mock() widget.dragMoveEvent(event) assert event.acceptProposedAction.called class TestDropEvent: """Test dropEvent method""" @patch('pyPhotoAlbum.mixins.asset_drop.AddElementCommand') def test_drop_creates_image_element(self, mock_cmd_class, qtbot): """Test dropping image file creates ImageData element""" widget = TestAssetDropWidget() qtbot.addWidget(widget) widget.zoom_level = 1.0 widget.pan_offset = [0, 0] # Mock update method widget.update = Mock() # Setup project with page 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] # Mock asset manager mock_window.project.asset_manager = Mock() mock_window.project.asset_manager.import_asset = Mock(return_value="/imported/image.jpg") # Mock history mock_window.project.history = Mock() # Mock page renderer mock_renderer = Mock() mock_renderer.is_point_in_page = Mock(return_value=True) mock_renderer.screen_to_page = Mock(return_value=(100, 100)) # Mock _get_page_at to return tuple widget._get_page_at = Mock(return_value=(page, 0, mock_renderer)) widget._page_renderers = [(mock_renderer, page)] widget.window = Mock(return_value=mock_window) # Create drop event mime_data = QMimeData() mime_data.setUrls([QUrl.fromLocalFile("/path/to/image.jpg")]) event = Mock() event.mimeData = Mock(return_value=mime_data) event.position = Mock(return_value=QPoint(150, 150)) event.acceptProposedAction = Mock() widget.dropEvent(event) # Should have called asset manager assert mock_window.project.asset_manager.import_asset.called # Should have created command assert mock_cmd_class.called # Should have executed command assert mock_window.project.history.execute.called assert widget.update.called def test_drop_outside_page_does_nothing(self, qtbot): """Test dropping outside any page does nothing""" widget = TestAssetDropWidget() qtbot.addWidget(widget) widget.zoom_level = 1.0 widget.pan_offset = [0, 0] mock_window = Mock() mock_window.project = Project(name="Test") page = Page(layout=PageLayout(width=210, height=297), page_number=1) mock_window.project.pages = [page] # Mock renderer that returns False (not in page) mock_renderer = Mock() mock_renderer.is_point_in_page = Mock(return_value=False) widget._page_renderers = [(mock_renderer, page)] widget.window = Mock(return_value=mock_window) mime_data = QMimeData() mime_data.setUrls([QUrl.fromLocalFile("/path/to/image.jpg")]) event = Mock() event.mimeData = Mock(return_value=mime_data) event.position = Mock(return_value=QPoint(5000, 5000)) event.acceptProposedAction = Mock() widget.dropEvent(event) # Should not create any elements assert len(page.layout.elements) == 0 def test_drop_updates_existing_placeholder(self, qtbot): """Test dropping on existing placeholder updates it with image""" widget = TestAssetDropWidget() qtbot.addWidget(widget) widget.zoom_level = 1.0 widget.pan_offset = [0, 0] widget.update = Mock() # Setup project with page containing placeholder 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) from pyPhotoAlbum.models import PlaceholderData placeholder = PlaceholderData(x=100, y=100, width=200, height=150) page.layout.elements.append(placeholder) mock_window.project.pages = [page] # Mock renderer mock_renderer = Mock() mock_renderer.is_point_in_page = Mock(return_value=True) mock_renderer.screen_to_page = Mock(return_value=(150, 150)) widget._page_renderers = [(mock_renderer, page)] widget.window = Mock(return_value=mock_window) # Mock element selection to return the placeholder widget._get_element_at = Mock(return_value=placeholder) mime_data = QMimeData() mime_data.setUrls([QUrl.fromLocalFile("/path/to/image.jpg")]) event = Mock() event.mimeData = Mock(return_value=mime_data) event.position = Mock(return_value=QPoint(150, 150)) event.acceptProposedAction = Mock() widget.dropEvent(event) # Should replace placeholder with ImageData assert len(page.layout.elements) == 1 assert isinstance(page.layout.elements[0], ImageData) assert page.layout.elements[0].image_path == "/path/to/image.jpg" @patch('pyPhotoAlbum.mixins.asset_drop.AddElementCommand') def test_drop_multiple_files(self, mock_cmd_class, qtbot): """Test dropping first image from multiple files""" widget = TestAssetDropWidget() qtbot.addWidget(widget) widget.zoom_level = 1.0 widget.pan_offset = [0, 0] widget.update = Mock() 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] mock_window.project.asset_manager = Mock() mock_window.project.asset_manager.import_asset = Mock(return_value="/imported/image1.jpg") mock_window.project.history = Mock() mock_renderer = Mock() mock_renderer.is_point_in_page = Mock(return_value=True) mock_renderer.screen_to_page = Mock(return_value=(100, 100)) widget._get_page_at = Mock(return_value=(page, 0, mock_renderer)) widget._page_renderers = [(mock_renderer, page)] widget.window = Mock(return_value=mock_window) # Create drop event with multiple files (only first is used) mime_data = QMimeData() mime_data.setUrls([ QUrl.fromLocalFile("/path/to/image1.jpg"), QUrl.fromLocalFile("/path/to/image2.png"), QUrl.fromLocalFile("/path/to/image3.jpg") ]) event = Mock() event.mimeData = Mock(return_value=mime_data) event.position = Mock(return_value=QPoint(150, 150)) event.acceptProposedAction = Mock() widget.dropEvent(event) # Only first image should be processed assert mock_window.project.asset_manager.import_asset.call_count == 1 def test_drop_no_project_does_nothing(self, qtbot): """Test dropping when no project loaded does nothing""" widget = TestAssetDropWidget() qtbot.addWidget(widget) widget.update = Mock() mock_window = Mock() mock_window.project = None widget.window = Mock(return_value=mock_window) # Mock _get_element_at to return None (no element hit) widget._get_element_at = Mock(return_value=None) mime_data = QMimeData() mime_data.setUrls([QUrl.fromLocalFile("/path/to/image.jpg")]) event = Mock() event.mimeData = Mock(return_value=mime_data) event.position = Mock(return_value=QPoint(150, 150)) event.acceptProposedAction = Mock() # Should not crash widget.dropEvent(event) # Should still accept event and call update assert event.acceptProposedAction.called assert widget.update.called